dist/codex/nlweb-protocol/skills/nlweb-prompts-customization/SKILL.md
Customize NLWeb's LLM prompts and per-Schema.org-type behavior via `prompts.xml` and `site_types.xml` — covers the `<promptString>` template format, `<returnStruc>` JSON schemas, prompt inheritance, decontextualization/ranking/generate templates, per-site overrides, and pitfalls of editing prompts in place. Use when tuning answer quality, supporting a new domain, or localizing prompts.
npx skillsauth add orcaqubits/agentic-commerce-claude-plugins nlweb-prompts-customizationInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Fetch live docs:
AskAgent/python/core/prompts.py to see how prompts are loaded and parameterized.methods/ to see which prompt each LLM call site uses.| File | Purpose |
|------|---------|
| prompts.xml | Defines <promptString> templates and their <returnStruc> JSON output contracts |
| site_types.xml | Maps Schema.org @type → tool list + per-type prompt overrides, with inheritance |
<promptString> TemplateA typical prompt entry:
<promptString name="decontextualize_query">
<description>Rewrite the user's query to be standalone, given prior conversation.</description>
<input>
<field name="prev_queries" type="array" />
<field name="current_query" type="string" />
</input>
<text>
Given the prior queries: {prev_queries}
And the current query: {current_query}
Return a standalone version of the current query.
</text>
<returnStruc>
{
"decontextualized": "the standalone query as a single string"
}
</returnStruc>
</promptString>
The exact tag names may differ — verify the live prompts.xml. The pattern: human-readable description, input schema, prompt text with {placeholders}, JSON output contract.
site_types.xml uses extends to inherit prompts up a type hierarchy:
<site_type name="Recipe" extends="CreativeWork">
<prompt name="rank_results" override="rank_results_recipe" />
<tool>recipe_substitution</tool>
</site_type>
When the handler asks for the rank_results prompt at runtime, it walks the type chain: Recipe → CreativeWork → default, taking the first override.
Common prompt types (verify against live file):
| Category | Used For |
|----------|----------|
| decontextualize_query | Rewrite "what about the second one?" → "tell me about <item X>" |
| detect_item_type | Identify Schema.org type the user is asking about |
| select_tool | Router decides which methods/ handler to invoke |
| rank_results | LLM scores retrieved items by relevance |
| summarize_results | mode=summarize condensation |
| generate_answer | mode=generate RAG synthesis |
| extract_key_fields | Pull specific Schema.org properties for the answer |
<returnStruc> ContractEvery LLM call has a strict JSON output schema. NLWeb parses the response as JSON and crashes if it doesn't validate. This is intentional — mixed-mode programming requires deterministic parsing.
Tips for writing <returnStruc>:
reasoning field — invaluable for debugging.Prompts are plain text — translate them to localize. NLWeb doesn't have first-class i18n; site operators fork the prompt file per locale (or load locale-specific files). Verify whether the runtime supports per-request locale selection in the current release.
To add prompts for a new Schema.org type (or a custom type):
<site_type name="YourType" extends="..."/> in site_types.xml.<promptString> entries with names like rank_results_yourtype, generate_answer_yourtype in prompts.xml.<prompt name="rank_results" override="rank_results_yourtype"/> in the site_type.Prompts are upgrade-fragile. Strategy:
prompts.xml to a site-specific file (e.g., prompts_mysite.xml).prompts_file: setting per site — varies by release).In order of cost-effectiveness:
config_llm.yaml).<returnStruc> so the model can't ramble.tool_selection_enabled: false to bypass routing and isolate the prompt under test./ask with mode=list to skip generate-mode prompts entirely.reasoning field to the <returnStruc> and log it.core/prompts.py to see the fully-interpolated prompt.<returnStruc> is too complex, model tier too low, or prompt text is ambiguous. Bump to high tier and simplify.extends or override attribute; check exact XML attribute names against the live file.<returnStruc> field names must stay English; only the human-facing prompt text translates./ask triggers multiple LLM calls; even a small prompt-length increase multiplies.A single /ask invocation may use 3-5 different prompts (decontextualize, select tool, rank, summarize). Editing one only affects that call site. Use call-site logs to confirm which prompt fired.
Prompts are config — version them in git alongside your other config files. When upgrading NLWeb, diff the upstream prompts.xml against yours to catch new prompts you should adopt.
Always re-fetch prompts.xml and site_types.xml from the live repo before customizing — XML attribute names and prompt template syntax evolve.
development
Build with Spree's headless Next.js storefront — the official `spree/storefront` repo (Next.js 16 App Router with Server Actions and Turbopack, React 19 Server Components, Tailwind CSS 4, TypeScript 5, `@spree/sdk`, Sentry), server-only auth (httpOnly JWT cookies + publishable key), MeiliSearch faceted catalog, one-page checkout with Apple/Google Pay/Klarna/Affirm/SEPA, multi-region market routing, GA4 + JSON-LD SEO, and Vercel/Docker deployment. Use when forking or customizing the storefront, or evaluating headless adoption.
tools
Build Spree extensions as Rails engines — gem scaffolding, `bin/rails g spree:extension`, mounting routes/migrations/assets, the modern `prepend` decorator pattern (`*_decorator.rb` with `self.prepended(base)`), generators (`spree:model_decorator`, `spree:controller_decorator`), the four customization surfaces in preference order (Events > Webhooks > Dependencies > Decorators), Spree::Dependencies for swapping service objects, gem release/versioning, and the deprecated Deface engine. Use when building a reusable Spree extension or adding non-trivial customization to an app.
development
Build with Spree's event bus and Webhooks 2.0 — `Spree::Events` publication, `Spree::Subscriber` DSL with `subscribes_to` and `on`, wildcard matching, lifecycle events (`{model}.created/.updated/.deleted` via `publishes_lifecycle_events`), the canonical event catalog (order.*, payment.*, shipment.*, product.*), Webhooks 2.0 endpoints, HMAC-SHA256 signing (`X-Spree-Webhook-Signature`), exponential-backoff retries, and Sidekiq job orchestration. Use when wiring event-driven business logic, building webhook consumers, or replacing ActiveSupport callback chains.
tools
Cross-cutting Spree development patterns — the customization preference hierarchy (Events > Webhooks > Dependencies > Decorators), `Spree::Dependencies` service-object swapping, the `_decorator.rb` + `prepend` + `self.prepended` idiom, idempotent subscribers and webhook receivers, multi-store scoping discipline, prefixed IDs, calculator polymorphism (shipping/promotion/tax share the base), service-object composition with `dry-monads` or simple results, why to avoid `class_eval` reopening and Deface, and Spree-on-Rails idioms (Hotwire/Turbo Stimulus, ActiveStorage, Action Cable, Sidekiq). Use when designing the architecture of a Spree extension or solving cross-cutting concerns.