Marqo AI-Native Ecommerce Search Platform
repository·mainline·Indexed 11 days ago
https://github.com/marqo-ai/marqoAn AI-native ecommerce search platform utilizing semantic search and personalization to deliver product results and recommendations. The system includes an Inference Orchestrator for model management and vectorization via NVIDIA Triton, and integrates with Vespa for search infrastructure.
What's inside Marqo
- Marqo is an AI-native ecommerce search platform designed for online brands (fashion, beauty, electronics, home goods). It uses semantic search and personalization technology, leveraging clickstream, purchase, and event data to understand shopper intent. The platform is intended to improve search relevance, increase conversion and average order value, and automate ranking and merchandising.
Introduction to Multimodal Search in Marqo
mainlineMultimodal search allows you to operate over multiple data types simultaneously, such as text and images. In Marqo, this is achieved through two primary methods:
- Multimodal Queries: Using a combination of text and images to perform a search.
- Multimodal Documents: Indexing documents that contain both text and images.
By combining these modalities, you can capture complementary information that a single modality might miss (e.g., using text to disambiguate the subject of an image). This approach enables advanced features like searching via natural language prompting, per-query personalization, and incorporating business logic or relevance feedback directly into the search experience without retraining models.
Personalize search with context vectors
mainlineTo personalize search using a set of items (like 'popular' or 'liked' products), you can use a
contextobject in the.search()method.Workflow:
- Create a separate index for context.
- Create documents representing the set of items using
multimodal_combinationmappings to define how fields are weighted. - Retrieve the embedding (tensor) of these documents using
.get_documents(). - Pass the retrieved embedding into the
.search()method using thecontextparameter.
# 1. Retrieve the embedding from the context index indexed_documents = client.index(index_name_context).get_documents([document1['_id']], expose_facets=True) context_vector = indexed_documents['results'][0]['_tensor_facets'][0]['_embedding'] # 2. Create the context object context = { "tensor": [ {'vector': context_vector, 'weight': 0.50} ] } # 3. Search with context query = {"backpack": 1.0} res = client.index(index_name).search(query, device=device, limit=10, context=context)Wrangle audio data for Marqo indexing
mainlineSince Marqo does not natively support audio files, you must build a processing pipeline that converts audio into text or images (spectrograms) before indexing. A common pattern is to use an
AudioWranglerclass to ingest audio from various sources (YouTube, web URLs, or local files), convert them to a standard format like.wav, and then perform speaker diarisation and speech-to-text to create text documents that Marqo can index.The typical workflow is:
- Ingestion: Download and normalize audio.
- Processing: Perform speaker diarisation and speech-to-text.
- Indexing: Send the resulting transcriptions to Marqo.
- Retrieval: Search the index to answer questions about the audio content.
class AudioWrangler(): def __init__(self, output_path: str, clean_up: bool = True): self.output_path = output_path self.tmp_dir = 'downloads' # ... initialization logic ... def convert_to_wav(self, fpath: str): # Converts audio to WAV format using Pydub pass def download_from_youtube(self, url: str): # Extracts audio from YouTube as MP3 then converts to WAV pass def download_from_web(self, url: str): # Downloads audio from a direct URL passHow the Inference Orchestrator request flow works
mainlineThe service follows a specific lifecycle for processing inference requests:
- Request Reception: A client sends a MessagePack encoded request to the
/vectoriseendpoint. - Validation: The request is validated using Pydantic schemas.
- Cache Lookup: The service checks the Inference Cache (using LRU or LFU strategies) for existing results.
- Cache Miss Workflow:
- Media Processing: Media (images, text, etc.) is downloaded and preprocessed.
- Pipeline Selection: The appropriate Inference Pipeline (e.g., HuggingFace or OpenCLIP) is selected.
- Model Management: The Model Manager loads the required model if it is not already in memory.
- Inference Execution: The request is sent to the NVIDIA Triton inference server.
- Caching: The resulting embeddings are stored in the cache.
- Response: The final result is returned to the client as a MessagePack encoded response.
- Request Reception: A client sends a MessagePack encoded request to the
Condition search using relevance feedback (context vectors)
mainlineYou can steer search results toward specific themes or items (e.g., items a user has liked or purchased) by using a context vector. This acts as a form of relevance feedback. To avoid inference latency at search time, you can pre-compute the vectors for a set of items and fuse them into a single context vector to be used alongside your primary query.
# Conceptual representation of combining a query with a pre-computed context vector query = {"backpack": 1.0} context_vector = [.1, ..., -.8] # Pre-computed from a set of itemsUse Marqo highlights for context-aware LLM prompting
mainlineWhen augmenting an LLM with retrieved context, you can use Marqo's search highlights to perform token-aware truncation. This ensures that the text provided to the LLM includes relevant context from immediately before and after the most relevant match, helping to stay within token limits while maintaining semantic coherence.
In the provided workflow, the
extract_text_from_highlightsfunction (a utility used in the example) takes the searchresultsand atoken_limitto return the specific text segments to be used assummariesin a Langchain prompt.# Example of preparing context from search results highlights, texts = extract_text_from_highlights(results, token_limit=150) # Formatting for Langchain Document objects docs = [Document(page_content=f"Source [{ind}]:" + t) for ind, t in enumerate(texts)]Perform zero-shot classification using Marqo
mainlineYou can perform zero-shot classification by creating a separate index containing only your category labels (e.g.,
[{"label": "a face"}, {"label": "a hamburger"}]). By searching for an image against this label index, the returned_scorefor each label represents the confidence of that classification.# Create a label index labels = [{"label": "one hot dog"}, {"label": "two hot dogs"}, {"label": "a hamburger"}, {"label": "a face"}] client.create_index(index_name, settings_dict=settings) client.index(index_name).add_documents(labels, tensor_fields=["label"]) # Classify an image by searching for it in the label index # The returned hits will contain the scores for each label responses = client.index(index_name).search(image_url, device='cpu') for hit in responses['hits']: print(f"Label: {hit['label']}, Score: {hit['_score']}")Set up the Marqo Simple CLI Demo
mainlineTo run the Marqo Simple CLI Demo, follow these steps to prepare your environment, dataset, and Marqo container:
- Prerequisites: Ensure you have Python 3.8 installed.
- Dataset: Download the Clothing Dataset and place it in the same directory as the
simple_marqo_demo.pyscript. - Local File Server: Start a local HTTP server in the script directory so the Marqo Docker container can access your local files:
python3 -m http.server 8222 - Run Marqo via Docker: Start the Marqo container with host gateway access enabled:
docker run --name marqo -it -p 8882:8882 --add-host host.docker.internal:host-gateway marqoai/marqo:latest - Install Client: Install the Marqo Python library:
pip install marqo - Execute Demo: Run the demonstration script:
python3 simple_marqo_demo.py
# 1. Start local file server python3 -m http.server 8222 # 2. Run Marqo container docker run --name marqo -it -p 8882:8882 --add-host host.docker.internal:host-gateway marqoai/marqo:latest # 3. Install marqo pip install marqo # 4. Run the demo python3 simple_marqo_demo.pyRun the Marqo Inference Orchestrator service
mainlineDepending on your environment, you can run the service in development mode or production mode.
Development mode: Run the module directly using
python -m.Production mode: Use
uvicornto serve the FastAPI application on a specific host and port.Docker: You can also run the service as a container.
Requirements:
- NVIDIA Triton inference server (required for production deployments)
# Development mode PYTHONPATH=./src python -m inference_orchestrator.main # Production mode PYTHONPATH=./src uvicorn inference_orchestrator.main:app --host 0.0.0.0 --port 8884 # Docker build and run docker build -t marqo-inference . docker run -p 8884:8884 marqo-inferenceIndex documents in Marqo
mainlineDocuments must be formatted as Python dictionaries for ingestion. Each dictionary should contain a
textfield and optional metadata likesource. After preparing your list of documents, use themarqo.Clientto create an index and ingest the data. If no specific encoder is provided, Marqo uses its default encoder.from marqo import Client # Prepare documents document1 = {"text":"Auto-Off function: This feature automatically switches off the steam iron if it has not been moved for a while.", "source":"page 1"} documents = [document1, document2, document3, document4, document5] # Create index and ingest mq = Client() index_name = "iron-docs" mq.create_index(index_name) # Note: In a full implementation, you would call mq.index(index_name).add_documents(documents)from marqo import Client document1 = {"text":"Auto-Off function: This feature automatically switches off the steam iron if it has not been moved for a while.", "source":"page 1"} # other document content left out for clarity documents = [document1, document2, document3, document4, document5] from marqo import Client mq = Client() index_name = "iron-docs" mq.create_index(index_name)Run Multi-node Vespa cluster
mainlineTo simulate a distributed environment, you can run a multi-node Vespa cluster. The cluster composition is determined by the number of shards and replicas provided:
- Config nodes: 3 nodes.
- Content nodes:
mnodes, wherem = number_of_shards * (1 + number_of_replicas). - API nodes:
nnodes, wheren = max(2, number_of_content_nodes).
Example: Using
--Shards 2 --Replicas 1results in 4 content nodes and 2 API nodes.python vespa_local.py start --Shards 2 --Replicas 1