docs: lock inference review — prompts, Call 1/2, feat tags

Restore CONTENT-WOO, add PROMPTS.md, and align PRD/architecture/API
with inference decisions: prompt files on disk, 0.7 confidence gate,
species constrained to Woo leaves, Call 2 prose-only + templates,
feat-* tags, and inference_enabled gating.
This commit is contained in:
Ty
2026-08-31 17:11:05 -07:00
parent 4e97770373
commit 4d9f3070a7
10 changed files with 420 additions and 33 deletions
+178
View File
@@ -0,0 +1,178 @@
# Inference Prompts
Status: SPEC · 2026-08-27 · SlabUploader
Prompts are server-side text files, not settings values. Read fresh on each
invocation. Shipped defaults live in `backend/prompts/`.
## Management
- **Location:** `/app/prompts/` inside the FastAPI container, mounted read-only
from host `prompts/`.
- **Editing:** Edit the text files on the host. No server restart needed.
- **Versioning:** The inference_log stores the prompt content (or hash) with each
inference call, so you can trace which prompt version produced which result.
- **Reset-to-default:** `python -m app.prompts.reset` restores shipped defaults
from the app bundle.
## Call 1 — Vision (species/character classification)
File: `/app/prompts/call1.txt`
### Purpose
Classify the slab's species, edge type, figure, and grade by analyzing
inventory photos. Constrained to choose from the synced Woo taxonomy lists.
### Inputs assembled by the server
- 1–5 inventory photos, downscaled to 1024px max
- Species leaf categories from Woo (list with IDs and brief definitions)
- Edge Type attribute terms with IDs
- Figure attribute terms with IDs
- Grade attribute terms with IDs
- User metadata: SKU, length, thickness
### System prompt (template)
```
You are an expert wood slab classifier. Analyze the provided slab photos and
return taxonomy data. Only use the values provided in the taxonomy lists — do
not invent categories or attribute values.
```
### User prompt (assembled at runtime)
```
Analyze the following wood slab photos and classify the species, edge type,
figure, and grade.
Species options (choose ONLY from this list):
1. [species name] — [brief definition] (woo_id: <id>)
2. ...
Edge Type options:
- Live Edge — natural, uncut bark edge
- Flat Sawn — squared, milled edges
...
Figure options:
- Cathedral — U-shaped grain patterns
- Burl — swirling, knotted grain
...
Grade options:
- Premium — minimal defects, clean grain
- Standard — some minor characteristics
...
Slab metadata: SKU={sku}, Length={length}in, Thickness={thickness}in
Return valid JSON matching this schema:
{
"species_id": <woo_id or null>,
"species_confidence": 0.0-1.0,
"wood_category_ids": [<woo_id>, ...],
"edge_type_term_id": <woo_id or null>,
"figure_term_ids": [<woo_id>, ...],
"figure_confidence": 0.0-1.0,
"grade_term_ids": [<woo_id>, ...],
"grade_confidence": 0.0-1.0,
"character": { "type": "string", "severity": "string" },
"inclusions": { "type": "string", "severity": "string" },
"voids": "string",
"checks": "string",
"wood_categories": [
{ "name": "string", "confidence": 0.0-1.0 },
...
]
}
```
### Notes
- The taxonomy list (species, edge type, figure, grade) is pulled from the
synced Woo cache each time, so the options always match what's in the store.
- Brief definitions help the model distinguish ambiguous cases
(e.g., "quarter-sawn" vs "cathedral").
- Character/inclusions/voids/checks are free-text observations. The server
maps them to Woo `feat-*` product tags after the call returns. WooCommerce
is the authoritative source for feat-* tag definitions; the design does not
duplicate them.
---
## Call 2 — Content (description prose)
File: `/app/prompts/call2.txt`
### Purpose
Generate compelling product description prose for the slab. The model writes
only the descriptive portion — dimensions, species, and measurements are
injected by deterministic templates.
### Inputs assembled by the server
- Curated Call 1 results (species, figure, character, grade)
- Deterministic measurements: length, widths, sqft, bdft, thickness
- Brand voice
- GEO context
- Title template and short description template
### System prompt (template)
```
You are a professional product copywriter for premium, one-of-a-one wood
slabs. Write compelling, natural-sounding descriptions. Use the brand voice
provided. Do not invent measurements — use only the provided data.
```
### User prompt (assembled at runtime)
```
Write product copy for this wood slab.
Species: {species}
Figure: {figure}
Character: {character description from Call 1}
Length: {length_ft}ft {length_in_remainder}in
Average width: {width_avg_in}in
Width range: {width_min_in}in to {width_max_in}in
Thickness: {thickness_in}in
Board feet: {bdft} bdft
Grade: {grade}
Brand voice: {brand_voice}
GEO context: {geo_context}
Write:
1. A compelling opening sentence about the slab
2. A paragraph describing the character, grain, color, and use cases
3. A call to action
Keep the tone appropriate for a premium lumber buyer. Do not repeat the
dimensions — those are handled by the template. Focus on the beauty and
versatility of the slab.
Return JSON: { "prose": "..." }
```
### Template assembly (server-side, after LLM returns)
The server assembles the final title and description from templates with
deterministic measurements, inserting the LLM prose where appropriate.
Example title: `{species} {length_ft}ft × {width_avg}in Slab — {figure}`
Example description: `{llm_prose}. Each slab is naturally shaped — width varies
along the length at {width_min}in to {width_max}in. Sold by the slab;
dimensions as measured. {geo_sentence}`
### Notes
- The LLM never touches dimensions. Templates handle all numbers.
- No numeric-accuracy guardrail needed — dimensions never pass through the LLM.
- If `content_llm_enabled` is false, the LLM call is skipped and templates
are used with a generic description paragraph.
---
## Tuning workflow
1. Deploy the default prompts
2. Capture a real slab, let Call 1 run
3. Review the results against your expert judgment
4. Edit `call1.txt` to improve classification accuracy
5. Re-run Call 1 via `POST /api/v1/slabs/{id}/infer-taxon` to compare
6. Iterate until results are consistently accurate
The inference_log stores each iteration's input/output so you can track
progress across prompt versions.