Make OpenAI-family strict mode routable and stop redacting payee words

Strict structured-output mode rejects uniqueItems, so every request to a
gpt-5.6-family zero-data-retention endpoint failed with HTTP 400 behind
a generic error; duplicates were already rejected server-side, so the
keyword leaves the wire schemas, pinned by a strict-keyword allowlist
test built from the ledger that hit this.

The bare-BIC redaction pattern deleted every 8- and 11-letter word —
Openbank, BAUMARKT, RACETRACKER — blinding the model to the payee it
was asked to classify and tripping the unsafe-merchant check on honest
answers. BICs now die only labeled or attached to their IBAN, account
labels join the redaction secrets, an identifier-shaped merchant name
degrades to a merchant-less proposal instead of failing the row, and a
provider error inside an HTTP 200 envelope is reported as such (numeric
code only) instead of as envelope corruption.
This commit is contained in:
Lars Nolden
2026-09-12 23:17:24 +02:00
parent ec99434002
commit c5999adb1b
8 changed files with 223 additions and 25 deletions
+1 -1
View File
@@ -296,7 +296,7 @@ func (c candidateSet) schema() map[string]any {
"merchant_id": map[string]any{"type": []string{"string", "null"}, "enum": merchantEnums},
"new_merchant": map[string]any{"type": []string{"string", "null"}, "maxLength": 100},
"category_id": map[string]any{"type": "string", "enum": candidateIDs(c.categories)},
"tag_ids": map[string]any{"type": "array", "uniqueItems": true, "maxItems": len(tagIDs), "items": tagItems},
"tag_ids": map[string]any{"type": "array", "maxItems": len(tagIDs), "items": tagItems},
"confidence": map[string]any{"type": "string", "enum": []string{"high", "medium", "low"}},
},
}