from typing import Literal, Optional from pydantic import BaseModel, Field SortOption = Literal["relevance", "price_asc", "price_desc", "name_asc", "name_desc"] class SearchRequest(BaseModel): query: str = Field(..., min_length=1, max_length=500, description="Search query text") top_n: int = Field(20, ge=1, le=100, description="Number of results to return") sort_by: SortOption = Field("relevance", description="Sort order") text_weight: float = Field(0.5, ge=0.0, le=1.0, description="Text vs image weight for multimodal queries") image_b64: Optional[str] = Field(None, description="Base64-encoded image for multimodal search") class SearchResultItem(BaseModel): sku: str name: str brand: str price: float color: str color_family: str category: str gender: str image_url: str url: Optional[str] = None score: float style_tags: list[str] = [] in_stock: bool = True class QueryInfo(BaseModel): original_query: str processed_query: str detected_language: str = "en" was_translated: bool = False was_spell_corrected: bool = False spell_suggestion: Optional[str] = None parsed_category: Optional[str] = None parsed_color: Optional[str] = None parsed_price_range: list[Optional[float]] = [None, None] parsed_gender: Optional[str] = None parsed_style_tags: list[str] = [] parsed_material: Optional[str] = None parsed_size: Optional[str] = None parsed_exclusions: list[str] = [] sort_by: str = "relevance" available_sorts: list[str] = [] suggested_searches: list[str] = [] class SearchResponse(BaseModel): results: list[SearchResultItem] query_info: QueryInfo total: int class ImageSearchRequest(BaseModel): top_n: int = Field(20, ge=1, le=100) class EvaluateRequest(BaseModel): test_queries: list[dict] k_values: list[int] = [5, 10, 20]