Product Search Tool + Prompt Routing (consumer-agent)
1. Problem Statement
Section titled “1. Problem Statement”Agent latency is tied to tool call count × latency. The current dual-search pattern (search_products + web_search in parallel) triggers 2-3 BrightData calls per product query (6-30s for comparisons). CCS now provides a single POST /v1/products/search/enriched endpoint that performs BrightData Google Shopping search server-side with 3-tier caching (in-memory → Valkey → live), Button partner matching, and PPD scoring — returning fully enriched results in one call.
2. Background & Context
Section titled “2. Background & Context”Current Tool Stack
Section titled “Current Tool Stack”| Tool | Backend | Latency | Data Quality |
|---|---|---|---|
search_products | pam-ml-search (rover-mcp) | ~1s | Poor — no prices, images, offers |
search_fidora_products | FIDORA (rover-mcp) | ~1s | Moderate — prices, images, but no offers/PPD |
web_search (BrightData) | BrightData SERP (client-side) | 3-10s | Good — prices, images, PDPs, but no offers |
product_search | CCS (BrightData server-side) | <2s (cached) | Best — prices, images, URLs, PPD, Button partner data |
What CCS Does Server-Side
Section titled “What CCS Does Server-Side”The CCS search/enriched endpoint runs the same BrightData pipeline that web_search does today, but:
- Server-side caching: 3-tier (in-memory 10min → Valkey 30d → live BrightData). Repeated/similar queries are fast.
- Per-product Button partner matching: Each result matched to best PPD merchant.
- CDN normalized images: Applied when FIDO match exists.
- No BrightData credentials needed in consumer-agent: CCS handles it.
Fan-Out Elimination
Section titled “Fan-Out Elimination”"Compare Tide vs Gain" → current: search_products + 2-3 web_search calls (6-20s) → proposed: 1 product_search call (<2s cached, ~5s cold)3. Requirements
Section titled “3. Requirements”Functional
Section titled “Functional”ProductSearchTool
Section titled “ProductSearchTool”- Natural language query input
- Calls CCS
POST /v1/products/search/enrichedendpoint - Returns: name, price, image, URL, retailer, PPD points, merchant info, availability
fido_idis optional in results (web-sourced products may not have one)sourcefield indicates data origin (“web”, “fidora”, “catalog”)- Optional
user_idfor future offer eligibility filtering
General Web Search
Section titled “General Web Search”- OpenAI native web search (
{"type": "web_search_preview"}) - No BrightData credentials needed
- For: reviews, ratings, non-grocery, recalls, news, external web context
- NOT for product discovery/comparison — product_search handles that
Prompt Routing
Section titled “Prompt Routing”product_searchfor ALL product queries: discovery, comparison, price checks, availability, recommendations, cart planningweb_searchONLY for: reviews, ratings, non-grocery products, external web context, news- No “web search fallback for non-catalog products” — CCS already searches the web
Tool Removal (when product_search flag is on)
Section titled “Tool Removal (when product_search flag is on)”- Stop calling
search_productsandsearch_fidora_productsfrom rover-mcp - Remove BrightData
WebSearchTool(CCS handles BrightData server-side) - Replace with OpenAI native web search for non-product queries
Acceptance Criteria
Section titled “Acceptance Criteria”- ProductSearchTool returns enriched data from CCS in single call
- OpenAI native web search replaces BrightData for non-product queries
- Prompt routes product queries to product_search only (no dual-search)
- Product-card component works with single-source CCS results
- Feature flag controls tool availability
- Latency improvement validated in staging
4. Solution Design
Section titled “4. Solution Design”4a. CCS Client
Section titled “4a. CCS Client”import httpx
class CCSClient: def __init__(self, base_url: str, timeout: float = 10.0): self.base_url = base_url self.timeout = timeout
async def search_products_enriched( self, query: str, limit: int = 5, user_id: str | None = None ) -> dict: async with httpx.AsyncClient() as client: resp = await client.post( f"{self.base_url}/v1/products/search/enriched", json={"query": query, "limit": limit, "user_id": user_id}, timeout=self.timeout, ) resp.raise_for_status() return resp.json()4b. ProductSearchTool
Section titled “4b. ProductSearchTool”class ProductSearchTool(BaseTool): name: str = "product_search" description: str = ( "Search for products available through Fetch Rewards partner retailers. " "Returns product details with prices, images, retailer info, and Fetch points-per-dollar (PPD). " "Use for product discovery, comparisons, price checks, availability, and recommendations. " "Do NOT use for product reviews, safety recalls, or non-grocery items — use web_search for those." ) args_schema: type[BaseModel] = ProductSearchInput _ccs_client: CCSClient
async def _arun(self, query: str, user_id: str | None = None) -> str: result = await self._ccs_client.search_products_enriched(query, user_id=user_id) return json.dumps(result, indent=2)4c. Factory Updates
Section titled “4c. Factory Updates”# factory.py — _build_tools()
# New product_search path (replaces both rover_mcp search + BrightData web_search)if feature_flags.get("product_search"): # Product search via CCS (BrightData + enrichment server-side) ccs_client = CCSClient(base_url=settings.ccs.base_url, timeout=settings.ccs.timeout) tool_list.append(ProductSearchTool(ccs_client=ccs_client))
# OpenAI native web search for non-product queries tool_list.append({"type": "web_search_preview"})
# Keep non-search MCP tools (offers, nearby, purchase history, etc.) if "rover_mcp" in enabled_tools: mcp_tools = build_mcp_tools(...) # Excludes search_products tool_list.extend([t for t in mcp_tools if t.name not in ("search_products",)])else: # Legacy path — unchanged ...existing rover_mcp + web_search logic...4d. Prompt Updates
Section titled “4d. Prompt Updates”product-card.yaml
Section titled “product-card.yaml”- Remove “Call search_products and web_search in parallel” workflow
- Replace with “Call product_search” (single source)
- Remove dual-source merge instructions
fidoIdbecomes optional (some CCS results are web-only)- All results already have price, image, URL from CCS
conversational.txt / conversational-xml.txt
Section titled “conversational.txt / conversational-xml.txt”- Product queries →
product_search(not search_products + web_search) - Web search → only non-product queries (reviews, news, recalls)
- Remove “call both tools in parallel” instructions
4e. Configuration
Section titled “4e. Configuration”# settings.yaml — all environmentsccs: base_url: "https://stage-consumer-context-service.us-east-1.stage-services.fetchrewards.com" timeout: 10.0Feature flag product_search is sent by rover-agent via feature_flags in the request payload (same mechanism as product_card and web_search flags today).
5. Dependencies
Section titled “5. Dependencies”- consumer-context-service:
POST /v1/products/search/enrichedendpoint (done — PR #69) - rover-mcp:
search_productsremoval AFTER consumer-agent stops calling it
6. Risks & Open Questions
Section titled “6. Risks & Open Questions”| Risk | Impact | Mitigation |
|---|---|---|
| CCS BrightData cold cache | First query for a product is slower (~5s) | 3-tier caching warms quickly; Valkey persists 30 days |
| Prompt routing failures | Agent uses web_search for product queries | Eval with product query dataset; explicit prompt guidance |
| CCS returns no Button partner match | Product has no PPD/merchant info | CCS falls back to raw BrightData data (still has price/image/retailer) |
Open questions:
- Should product_search results include a
fidoIdlookup? (Currently web results don’t have FIDO IDs) - Should we keep BrightData credentials as env vars for fallback, or remove entirely?
7. Testing Strategy
Section titled “7. Testing Strategy”- Unit tests: ProductSearchTool, CCSClient (mock CCS responses)
- Prompt routing eval: 50 product + 20 non-product queries
- Latency benchmark: product_search vs current search_products + web_search
- Regression: single-turn eval with product_search enabled
- Product-card rendering: verify single-source results display correctly
8. Rollout
Section titled “8. Rollout”Feature-flagged. product_search=true enables new path; false keeps legacy. Sequence:
- Deploy CCS endpoint (done)
- Merge consumer-agent changes (feature-flagged off)
- Enable in staging, run eval suite
- Enable in production (gradual rollout via feature flag)
- After stable: remove legacy search_products/web_search code paths