fix
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
# 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:
|
||||
|
||||
1. **Category create** — `catalog.ApplyDefaultCategoryFormulas` fills `prompt`,
|
||||
`title_template` and `description_template` on new categories (API create and
|
||||
CSV taxonomy import). Only empty columns are written, so nothing existing is
|
||||
overwritten.
|
||||
2. **Enhance-time fallback** — `processing.withDefaultCategoryFormula` supplies 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:
|
||||
|
||||
1. **The product has no category.** No category → no formula. Roughly 83% of the A1
|
||||
catalog has no `mapped_data.category` (see `scripts/seed/README.txt`); those rely
|
||||
on the `categorize` step, which needs a working LLM or Pinecone.
|
||||
2. **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 the `OPENAI_*` env values.
|
||||
3. **The plan/credits gate.** `ai_enhance skip reason=entitlement_can_use_ai` or
|
||||
`insufficient credits`.
|
||||
4. **The model ignored the formula.** One retry is issued
|
||||
(`descriptionFormulaRetrySuffix` / `titleRewriteRetrySuffix`); if it still fails,
|
||||
the result is marked `synthesized`/`refused` and no `enhance_input_hash` is
|
||||
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.
|
||||
Reference in New Issue
Block a user