Skip to content

Product Search Tool + Prompt Routing (consumer-agent)

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.

ToolBackendLatencyData Quality
search_productspam-ml-search (rover-mcp)~1sPoor — no prices, images, offers
search_fidora_productsFIDORA (rover-mcp)~1sModerate — prices, images, but no offers/PPD
web_search (BrightData)BrightData SERP (client-side)3-10sGood — prices, images, PDPs, but no offers
product_searchCCS (BrightData server-side)<2s (cached)Best — prices, images, URLs, PPD, Button partner data

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.
"Compare Tide vs Gain" → current: search_products + 2-3 web_search calls (6-20s)
→ proposed: 1 product_search call (<2s cached, ~5s cold)
  • Natural language query input
  • Calls CCS POST /v1/products/search/enriched endpoint
  • Returns: name, price, image, URL, retailer, PPD points, merchant info, availability
  • fido_id is optional in results (web-sourced products may not have one)
  • source field indicates data origin (“web”, “fidora”, “catalog”)
  • Optional user_id for future offer eligibility filtering
  • 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
  • product_search for ALL product queries: discovery, comparison, price checks, availability, recommendations, cart planning
  • web_search ONLY 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_products and search_fidora_products from rover-mcp
  • Remove BrightData WebSearchTool (CCS handles BrightData server-side)
  • Replace with OpenAI native web search for non-product queries
  • 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
src/consumer_agent/utils/ccs_client.py
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()
src/consumer_agent/tools/product_search.py
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)
# 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...
  • Remove “Call search_products and web_search in parallel” workflow
  • Replace with “Call product_search” (single source)
  • Remove dual-source merge instructions
  • fidoId becomes 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
# settings.yaml — all environments
ccs:
base_url: "https://stage-consumer-context-service.us-east-1.stage-services.fetchrewards.com"
timeout: 10.0

Feature 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).

  • consumer-context-service: POST /v1/products/search/enriched endpoint (done — PR #69)
  • rover-mcp: search_products removal AFTER consumer-agent stops calling it
RiskImpactMitigation
CCS BrightData cold cacheFirst query for a product is slower (~5s)3-tier caching warms quickly; Valkey persists 30 days
Prompt routing failuresAgent uses web_search for product queriesEval with product query dataset; explicit prompt guidance
CCS returns no Button partner matchProduct has no PPD/merchant infoCCS falls back to raw BrightData data (still has price/image/retailer)

Open questions:

  1. Should product_search results include a fidoId lookup? (Currently web results don’t have FIDO IDs)
  2. Should we keep BrightData credentials as env vars for fallback, or remove entirely?
  • 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

Feature-flagged. product_search=true enables new path; false keeps legacy. Sequence:

  1. Deploy CCS endpoint (done)
  2. Merge consumer-agent changes (feature-flagged off)
  3. Enable in staging, run eval suite
  4. Enable in production (gradual rollout via feature flag)
  5. After stable: remove legacy search_products/web_search code paths