fix
This commit is contained in:
@@ -8,7 +8,9 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"github.com/descrybe/descrybe-v2/apps/api/internal/aiprompts"
|
||||
"github.com/descrybe/descrybe-v2/apps/api/internal/company"
|
||||
"github.com/descrybe/descrybe-v2/apps/api/internal/eprel"
|
||||
)
|
||||
@@ -739,6 +741,10 @@ func appendStepLog(gpt map[string]any, name string, raw any) {
|
||||
// category description_template (short prose / title echo instead of multi-section HTML).
|
||||
const descriptionFormulaRetrySuffix = "\n\nINVALID DESCRIPTION. Your JSON ignored the Description formula. Reply with ONLY one JSON object; \"description\" must be ONE HTML string covering each formula section in order with matching tags (h1/h2/h3/h4, p, ul)."
|
||||
|
||||
// titleRewriteRetrySuffix is appended when the Title role requires a new retail
|
||||
// name but the model echoed the supplier Name unchanged.
|
||||
const titleRewriteRetrySuffix = "\n\nINVALID NAME. Category Title instructions require a NEW product name (formula / rewrite). \"name\" must NOT equal the supplier Name. Reply with ONLY one JSON object; rebuild \"name\" from type + brand + model + color (or the Title formula) in {{language}}."
|
||||
|
||||
// descriptionNeedsEnhanceRepair is true when desc is weak/empty/title-echo or
|
||||
// fails an active category description_template (A1 multi-section HTML).
|
||||
// Heuristic invent/synth is intentionally excluded here — enhanceHashForceReason
|
||||
@@ -750,6 +756,47 @@ func descriptionNeedsEnhanceRepair(desc string, template any, titles ...string)
|
||||
return !descriptionSatisfiesFormula(desc, template)
|
||||
}
|
||||
|
||||
// titlesAreEquivalent treats supplier vs enriched names as the same when only
|
||||
// case/spacing/punctuation differ — used to detect Title formula copy-paste.
|
||||
func titlesAreEquivalent(a, b string) bool {
|
||||
na := normalizeTitleCompare(a)
|
||||
nb := normalizeTitleCompare(b)
|
||||
if na == "" || nb == "" {
|
||||
return false
|
||||
}
|
||||
return na == nb
|
||||
}
|
||||
|
||||
func normalizeTitleCompare(s string) string {
|
||||
s = strings.TrimSpace(strings.ToLower(s))
|
||||
if s == "" || s == "<nil>" {
|
||||
return ""
|
||||
}
|
||||
var b strings.Builder
|
||||
b.Grow(len(s))
|
||||
prevSpace := false
|
||||
for _, r := range s {
|
||||
switch {
|
||||
case unicode.IsLetter(r) || unicode.IsDigit(r):
|
||||
b.WriteRune(r)
|
||||
prevSpace = false
|
||||
case unicode.IsSpace(r) || r == '-' || r == '_' || r == '/' || r == ',':
|
||||
if !prevSpace && b.Len() > 0 {
|
||||
b.WriteByte(' ')
|
||||
prevSpace = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return strings.TrimSpace(b.String())
|
||||
}
|
||||
|
||||
func titleNeedsRewriteRetry(in ProductInput, name string) bool {
|
||||
if !aiprompts.CategoryTitlePromptRequiresRewrite(in.CategoryEnhancePrompt) {
|
||||
return false
|
||||
}
|
||||
return titlesAreEquivalent(name, in.Name)
|
||||
}
|
||||
|
||||
// enhanceHashForceReason returns why a matching prior enhance_input_hash must not
|
||||
// skip the LLM (empty = safe to reuse as ai_enhance_unchanged).
|
||||
func enhanceHashForceReason(in ProductInput) string {
|
||||
@@ -762,10 +809,18 @@ func enhanceHashForceReason(in ProductInput) string {
|
||||
if !descriptionSatisfiesFormula(in.PriorProcessedDescription, in.DescriptionTemplate) {
|
||||
return "formula-mismatch"
|
||||
}
|
||||
if titleNeedsRewriteRetry(in, in.PriorProcessedName) {
|
||||
return "title-copy-paste"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (e *Engine) enhance(ctx context.Context, in ProductInput, category string, attrs map[string]any) (string, string, int, any, error) {
|
||||
// Prompt Description HTML overlays count as the formula when description_template
|
||||
// is empty — otherwise enhance accepts supplier-like prose and skips formula retry.
|
||||
if effective := aiprompts.EffectiveDescriptionTemplateAny(in.DescriptionTemplate, in.CategoryEnhancePrompt); effective != nil {
|
||||
in.DescriptionTemplate = effective
|
||||
}
|
||||
catUID := strings.TrimSpace(in.CategoryUniqueID)
|
||||
catName := strings.TrimSpace(category)
|
||||
sysTpl, userTpl := resolveProductPromptTemplates(in)
|
||||
@@ -885,11 +940,19 @@ func (e *Engine) enhance(ctx context.Context, in ProductInput, category string,
|
||||
// Quality-gate on LLM description alone (do not absorb originals yet).
|
||||
desc := preferredProductDescription(name, llmDesc)
|
||||
didFormulaRetry := false
|
||||
// Fast garbage that ignores A1 description_template: one formula-aware retry, then synthesize.
|
||||
if !descriptionSatisfiesFormula(desc, in.DescriptionTemplate) &&
|
||||
FormatDescriptionFormulaConstraint(in.DescriptionTemplate) != "" {
|
||||
needsDescRetry := !descriptionSatisfiesFormula(desc, in.DescriptionTemplate) &&
|
||||
FormatDescriptionFormulaConstraint(in.DescriptionTemplate) != ""
|
||||
needsTitleRetry := titleNeedsRewriteRetry(in, name)
|
||||
// One retry when Description HTML formula and/or Title rewrite were ignored.
|
||||
if needsDescRetry || needsTitleRetry {
|
||||
didFormulaRetry = true
|
||||
retryUser := user + descriptionFormulaRetrySuffix
|
||||
retryUser := user
|
||||
if needsDescRetry {
|
||||
retryUser += descriptionFormulaRetrySuffix
|
||||
}
|
||||
if needsTitleRetry {
|
||||
retryUser += titleRewriteRetrySuffix
|
||||
}
|
||||
retryStarted := time.Now()
|
||||
comp2, obj2, err2 := CompleteJSON(ctx, e.Completer, system, retryUser, CompleteOptions{
|
||||
MaxTokens: MaxTokensEnhance,
|
||||
@@ -904,7 +967,12 @@ func (e *Engine) enhance(ctx context.Context, in ProductInput, category string,
|
||||
n2 := SanitizeOutput(fmt.Sprint(obj2["name"]))
|
||||
d2 := SanitizeOutput(fmt.Sprint(obj2["description"]))
|
||||
if llmEnhanceHardRefuseReason(n2, d2) == "" {
|
||||
name = preferredProductTitle(in.GTIN, n2, name, in.Name, in.PriorProcessedName)
|
||||
// Prefer rewritten title over supplier echo when Title role requires it.
|
||||
if needsTitleRetry && n2 != "" && !titlesAreEquivalent(n2, in.Name) {
|
||||
name = preferredProductTitle(in.GTIN, n2)
|
||||
} else {
|
||||
name = preferredProductTitle(in.GTIN, n2, name, in.Name, in.PriorProcessedName)
|
||||
}
|
||||
desc = preferredProductDescription(name, d2)
|
||||
mt2, md2 := metaFieldsFromEnhanceObj(obj2)
|
||||
if mt2 != "" {
|
||||
|
||||
Reference in New Issue
Block a user