Skip to content

Add outcome-shopping-template: multi-vendor, outcome-based shopping orchestrator#970

Open
mconroy-cf wants to merge 3 commits intocloudflare:mainfrom
mconroy-cf:add-outcome-shopping-template
Open

Add outcome-shopping-template: multi-vendor, outcome-based shopping orchestrator#970
mconroy-cf wants to merge 3 commits intocloudflare:mainfrom
mconroy-cf:add-outcome-shopping-template

Conversation

@mconroy-cf
Copy link
Copy Markdown

Summary

Adds outcome-shopping-template — a multi-vendor, outcome-based shopping orchestrator that pairs with commerce-llms-txt-template.

Takes a natural-language shopping intent (e.g. "outfit my 3-year-old for skiing"), decomposes it with Workers AI into component product needs, and composes a cross-merchant recommendation from each merchant's commerce-llms-txt-template /api/products endpoint.

The React SPA at / runs the full decompose → match → compose pipeline against the bundled sample catalog, so the template demos itself end-to-end on first deploy.

What's in the template

  • Worker at src/orchestrator-worker/index.ts serving /api/shop, /api/catalogs, /api/merchants, /llms.txt, and an /api docs endpoint.
  • React + Vite SPA at src/react-app/ via @cloudflare/vite-plugin — single vite build produces both the SPA (as Workers Static Assets) and the Worker bundle.
  • Required CATALOG_CACHE KV binding with a placeholder ID in wrangler.jsonc. No optional fallback. Cache key incorporates the merchant fingerprint so changing MERCHANT_ENDPOINTS busts the cache automatically.
  • Bundled sample catalog (3 merchants, 9 products) matching the shape emitted by commerce-llms-txt-template, served when MERCHANT_ENDPOINTS is empty or every configured merchant fails.
  • Default AI model: @cf/google/gemma-4-26b-a4b-it, consistent with where we landed on commerce-llms-txt-template.
  • 8 vitest tests via @cloudflare/vitest-pool-workers.
  • Playwright E2E spec at playwright-tests/outcome-shopping-template.spec.ts.

Applying feedback from #968

Both new templates deliberately pre-apply the KV-required + SPA-on-top-of-API feedback from #968 so this reviewer doesn't have to re-litigate the same points.

Verification

  • pnpm turbo run check --filter=outcome-shopping-template — passes
  • pnpm turbo run test --filter=outcome-shopping-template — 8/8 passing
  • pnpm run check:templates — passes
  • pnpm run check:prettier — clean

matthewconroy added 2 commits April 22, 2026 17:41
Multi-vendor, outcome-based shopping orchestrator. Takes a natural-language
shopping intent, decomposes it with Workers AI, and composes a cross-merchant
recommendation from each merchant's commerce-llms-txt-template /api/products
endpoint. The React SPA at / runs the pipeline end-to-end against a bundled
sample catalog so the template is immediately useful after deploy.

- Required CATALOG_CACHE KV binding (cache key includes merchant fingerprint
  so config changes bust the cache automatically)
- @cloudflare/vite-plugin pattern: single vite build produces the SPA plus
  the Worker bundle; SPA owns /, Worker owns /api/* and /llms.txt
- Default AI model: @cf/google/gemma-4-26b-a4b-it
- Bundled sample catalog (3 merchants, 9 products) mirrors the shape emitted
  by commerce-llms-txt-template
- 8 vitest tests via @cloudflare/vitest-pool-workers
- Playwright e2e spec under playwright-tests/

Registered in templates.json.
The previous default, @cf/google/gemma-4-26b-a4b-it, is a reasoning
model on Workers AI. When called via the legacy `ai.run({messages})`
shape it writes its thinking to `message.reasoning` and only emits
`message.content` after, frequently hitting the default max_tokens
before the answer is produced. Result: decomposition silently falls
back to single-need mode, leaving deployments looking broken with no
obvious error.

Llama 3.3 70B FP8 Fast returns the structured JSON this pipeline
expects consistently under the same call shape, is featured on
Workers AI, and is fast enough for the per-need matcher. No other
code changes required — the response extractor already handles both
wire formats.

Verified: `pnpm turbo run check --filter=outcome-shopping-template`,
`pnpm run check:templates`, `pnpm run check:prettier`, and the 8
template tests all pass.
@mconroy-cf
Copy link
Copy Markdown
Author

Two updates since you received this.

Correctness fix in 49bb23e — switched the default AI_MODEL from @cf/google/gemma-4-26b-a4b-it to @cf/meta/llama-3.3-70b-instruct-fp8-fast. Gemma 4 is a reasoning model; called via ai.run({messages}) it writes to message.reasoning and frequently hits max_tokens before emitting message.content, so decomposition silently falls back to single-need mode and the template looks broken on first deploy. Llama 3.3 70B FP8 Fast returns the expected JSON reliably under the same call shape. The response extractor already handled both wire formats so no other code changed. Template tests, check, check:templates, and check:prettier all pass.

Reference implementation at gitlab.cfdata.org/mconroy/outcome-shopping has moved well past this template since the PR opened — streaming endpoint, stateful MCP agent (Durable Object per session), real Shopify merchants via a multi-tenant proxy, /demo auto-tour. Intentionally a separate artifact: this template is the minimal starter, the gitlab repo is where we've taken the concept further. Flagging it in case it's useful context while you review, not a request to roll any of it in.

KV keys are limited to 512 bytes. The previous cacheKey() built an
unbounded fingerprint by concatenating every merchant's name + URL,
which a fresh deploy with more than ~5 merchants (each URL contributes
~70-100 chars) can exceed \u2014 `KV.put()` would reject the write,
and the orchestrator would silently bypass the cache on every
request.

SHA-256 hashing the fingerprint produces a fixed-length 64-char hex
suffix that always fits. Collision risk is irrelevant: a collision
just serves another merchant set's cache, which triggers
invalidation on the next request anyway.

Caught while reviewing drift between the gitlab reference
implementation and this template (the gitlab side already had the
fix). No template tests change; this is a fresh-deploy correctness
fix that only manifests with several real merchants configured.
@mconroy-cf
Copy link
Copy Markdown
Author

Pushed 3499954: hash the catalog cache key so it fits KV's 512-byte key limit.

The previous cacheKey() concatenated each merchant's name + URL into an unbounded fingerprint. A fresh deploy with more than ~5 merchants (each URL ~70-100 chars) can exceed 512 bytes — KV.put() rejects the write and the orchestrator silently bypasses the cache on every request. SHA-256 hashing produces a fixed 64-char hex suffix that always fits.

Caught while reviewing drift between the gitlab reference implementation and this template — the gitlab side already had the fix. pnpm turbo run check --filter=outcome-shopping-template --force, pnpm run check:templates, and cd outcome-shopping-template && pnpm test all pass.

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.

1 participant