Skip to content

Unified batch enrichment: single API call per product#50

Open
rhoerr wants to merge 4 commits into
feature/enrichment-unit-testsfrom
feature/unified-batch-enrichment
Open

Unified batch enrichment: single API call per product#50
rhoerr wants to merge 4 commits into
feature/enrichment-unit-testsfrom
feature/unified-batch-enrichment

Conversation

@rhoerr

@rhoerr rhoerr commented Feb 15, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds ProductContextBuilder service that builds AI context from visible/searchable product attributes with configurable max-length truncation
  • Adds generateBatch() to AiClientInterface/OpenAiClient using OpenAI Structured Outputs (response_format: json_schema) to generate all attribute values in a single API call per product
  • Restructures Enricher into three phases (collect → generate → record) with automatic fallback to individual generate() calls if batch fails
  • Adds admin config field for Product Context Max Value Length (default: 500) in Advanced Settings

Design

Replaces per-attribute API calls with a single batch call per product, reducing API costs ~80% and improving content consistency across attributes. The batch schema enforces typed responses via OpenAI's json_schema response format with strict: true.

Falls back gracefully: if batch returns empty (parse error, API failure), each attribute is retried individually via the existing generate() method.

Test Plan

  • 11 tests for ProductContextBuilder (visible/searchable filtering, truncation, empty/null/array handling)
  • 6 tests for OpenAiClient batch helpers (schema building, prompt assembly, JSON parsing)
  • 14 tests for restructured Enricher (batch, cache hit/miss, partial cache, fallback, approval, deferred, overwrite)
  • Full suite: 146 tests, 316 assertions, 0 failures

Refs: #44

🤖 Generated with Claude Code

@rhoerr rhoerr changed the base branch from main to feature/enrichment-unit-tests February 15, 2026 04:27
rhoerr and others added 4 commits February 14, 2026 23:31
Extracts product context building into an injectable service behind
ProductContextBuilderInterface. Includes visible/searchable attribute
filtering, value truncation, and array/empty value exclusion.

Refs: #44

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implements structured JSON output using OpenAI response_format with
json_schema. Scales max_completion_tokens by attribute count. Returns
empty array on parse failure to signal fallback to individual calls.

Refs: #44

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replaces per-attribute enrichAttribute() loop with batch-oriented
three-phase execute(): collectPending checks cache and gathers work,
generate makes a single batch API call with individual fallback,
record persists enrichments and applies values to the product.

Refs: #44

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wires ProductContextBuilderInterface preference, adds
context_value_max_length config field (default: 500) to the
Advanced Settings group.

Refs: #44

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@DavidLambauer

Copy link
Copy Markdown
Contributor

Hey @rhoerr, before I do anything with this I want to surface the tension and save it for our next sync.

The work is real: 31 new tests, a clean collect → generate → record split, and the hybrid fallback is smart. ~80% API cost reduction is a big claim and it's plausible for stores enriching at scale.

What I'm stuck on:

  1. Cache key conflict with Add enrichment review, caching, and approval workflow (#28) #46. The enrichment cache keys on SHA-256(parsedPrompt|systemPrompt|attributeCode|storeId). Batch generation produces all attributes from one combined prompt, so when one attribute's input changes (say, the admin tweaks the description prompt), the batch cache miss regenerates every attribute even if the others would have been individual cache hits. On incremental updates, batch mode loses the cache benefits that are the whole point of the review workflow.

  2. Per-attribute prompt engineering gets flattened. With the dynamic attribute table from Make enrichment attributes configurable (#27) #45, admins configure different prompts per attribute. Short description and meta title want different tones, lengths, and ideally different temperatures. One batch call with one schema forces them into one shape. The strict json_schema helps with format but not with voice.

  3. Rate-limit backoff is hidden in the batch. The current per-call backoff responds to 429s per attribute. In batch mode, a rate-limit error aborts the whole product, falling back to individual calls, which is the worst path for both cost and latency.

  4. Fallback can undo the cost savings. If batch fails, we pay for the batch attempt plus N individual calls. On an unstable API day, that spikes costs rather than reduces them.

Not a rejection, just things I want to talk through in our sync. There's probably a version where batch is flag-gated per store (or per attribute group) and the cache accounts for partial hits, which would get us most of the upside without regressing #46's cache design. Let's chat.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants