prasadnu's picture
feat: add Search Personalization demo module
b4d5c9a
Raw
History Blame
4.8 kB
"""
Query Understanding Agent β€” Enriches raw search queries using user profile context.
Returns only the enriched query attributes; the orchestrator handles the actual search.
"""
from typing import Optional
from strands import Agent
from strands.models import BedrockModel
from search_personalization.agentic_memory.config import BEDROCK_MODEL_ID_FAST, AWS_REGION
from search_personalization.agentic_memory.schema_cache import get_schema_prompt_block
_DEFAULT_SCHEMA_BLOCK = """PRODUCT INDEX SCHEMA:
- category (keyword, 5 values): apparel, accessories, footwear, electronics, jewelry
- style (keyword): jacket, formal, shirt, glasses, scarf, backpack, sneaker, belt, handbag, bracelet, camera, sandals, boot, necklace, socks, watch, earrings, bag, speaker, headphones, cable, television, computer, keyboard
- gender_affinity (keyword): F, M
- price (numeric)
- name (text) β€” product name
- description (text) β€” product description"""
_SYSTEM_PROMPT_TEMPLATE = """You are the Query Understanding Agent. Enrich a search query using the user's behavioral profile.
{schema_block}
FIELD USAGE:
- category β†’ hard filter (exact match, 5 values)
- gender_affinity β†’ hard filter ("F" or "M")
- max_price β†’ hard filter (numeric ceiling) β€” ONLY when user explicitly states price in query
- enriched_query β†’ drives vector/semantic search (no filter values here)
ENRICHED QUERY RULES:
1. Format: space-separated keywords/phrases, NOT a sentence. Example: "shoes sneakers athletic comfortable black gray"
2. Start with the user's original query words (strip any price mentions like "under $50").
3. Append descriptors reflecting the user's DOMINANT style pattern (majority of behavior, not outliers). Example: 3 sneaker purchases + 1 dress shirt = sneaker person, NOT formal person.
4. If the profile shows strong preference for a product sub-type (e.g., bought 3 sneakers), include that sub-type. This is the most important signal.
5. Express aversions as positive opposites (e.g., aversion "flashy" β†’ add "understated"; "loud colors" β†’ "muted tones"). Do NOT use negation words.
6. NEVER include in enriched_query: price values ("under $50", "$50", "budget"), category names, gender letters, or any term related to the aversions (e.g., if aversions include "formal"/"fancy", never add "formal", "dress", "elegant", or similar).
7. Use conversation history to resolve references (e.g., "in blue" after "shoes" β†’ "shoes blue").
INFERRED ATTRIBUTES:
- category: REQUIRED. Map query to: apparel, accessories, footwear, electronics, jewelry.
- gender_affinity: "F" or "M" if any gender signal exists in profile. Null otherwise.
- max_price: numeric ONLY if the user's raw query text explicitly mentions a price constraint (e.g., "under $50", "below $100"). If the query does NOT contain a price mention, max_price MUST be null even if the profile suggests a budget ceiling.
- style: closest schema value or null.
- colors: recurring color preferences from profile.
- preferred_materials: recurring materials from profile.
- use_context: primary shopping context (e.g., "campus daily wear", "business-casual work").
- aversions: raw aesthetic dislikes from profile (e.g., ["flashy", "formal"]). Never include the searched product type.
OUTPUT (strict JSON, nothing else):
{{"enriched_query": "keywords only, e.g.: shoes sneakers athletic comfortable muted", "inferred_attributes": {{"colors": [], "style": null, "category": null, "gender_affinity": null, "max_price": null, "aversions": [], "preferred_materials": [], "use_context": null}}}}
"""
def create_query_agent() -> Agent:
"""Create the Query Understanding Agent (no tools β€” pure reasoning)."""
schema_block = get_schema_prompt_block() or _DEFAULT_SCHEMA_BLOCK
system_prompt = _SYSTEM_PROMPT_TEMPLATE.format(schema_block=schema_block)
model = BedrockModel(
model_id=BEDROCK_MODEL_ID_FAST,
region_name=AWS_REGION,
)
return Agent(
model=model,
tools=[],
system_prompt=system_prompt,
)
def invoke_query_agent(
query: str,
persona_id: str,
session_id: str,
profile: str = "",
session_context: str = "",
) -> str:
"""
Invoke the Query Understanding Agent.
Returns:
JSON string with enriched_query and inferred_attributes only.
"""
agent = create_query_agent()
profile_section = f"\n\nUser Profile:\n{profile}" if profile else "\n\nNo profile available β€” use query as-is."
history_section = f"\n\nConversation History:\n{session_context}" if session_context else ""
prompt = (
f"Process this search:\n\n"
f"Raw query: \"{query}\"{profile_section}{history_section}\n\n"
f"Enrich the query and output the JSON."
)
result = agent(prompt)
return str(result)