Marqo AI-Native Ecommerce Search Platform

repository·mainline·Indexed 11 days ago

https://github.com/marqo-ai/marqo

An 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.

Tokens
29.5K
Snippets
109
Records
123
Agent score
90%

What's inside Marqo

  1. Overview of Marqo

    mainline
    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.
  2. Introduction to Multimodal Search in Marqo

    mainline

    Multimodal search allows you to operate over multiple data types simultaneously, such as text and images. In Marqo, this is achieved through two primary methods:

    1. Multimodal Queries: Using a combination of text and images to perform a search.
    2. 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.

  3. Personalize search with context vectors

    mainline

    To personalize search using a set of items (like 'popular' or 'liked' products), you can use a context object in the .search() method.

    Workflow:

    1. Create a separate index for context.
    2. Create documents representing the set of items using multimodal_combination mappings to define how fields are weighted.
    3. Retrieve the embedding (tensor) of these documents using .get_documents().
    4. Pass the retrieved embedding into the .search() method using the context parameter.
    # 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)
  4. Wrangle audio data for Marqo indexing

    mainline

    Since 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 AudioWrangler class 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:

    1. Ingestion: Download and normalize audio.
    2. Processing: Perform speaker diarisation and speech-to-text.
    3. Indexing: Send the resulting transcriptions to Marqo.
    4. 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
            pass
  5. How the Inference Orchestrator request flow works

    mainline

    The service follows a specific lifecycle for processing inference requests:

    1. Request Reception: A client sends a MessagePack encoded request to the /vectorise endpoint.
    2. Validation: The request is validated using Pydantic schemas.
    3. Cache Lookup: The service checks the Inference Cache (using LRU or LFU strategies) for existing results.
    4. 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.
    5. Response: The final result is returned to the client as a MessagePack encoded response.
  6. Condition search using relevance feedback (context vectors)

    mainline

    You 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 items
  7. Use Marqo highlights for context-aware LLM prompting

    mainline

    When 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_highlights function (a utility used in the example) takes the search results and a token_limit to return the specific text segments to be used as summaries in 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)]
  8. Perform zero-shot classification using Marqo

    mainline

    You 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 _score for 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']}")
  9. Set up the Marqo Simple CLI Demo

    mainline

    To run the Marqo Simple CLI Demo, follow these steps to prepare your environment, dataset, and Marqo container:

    1. Prerequisites: Ensure you have Python 3.8 installed.
    2. Dataset: Download the Clothing Dataset and place it in the same directory as the simple_marqo_demo.py script.
    3. 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
    4. 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
    5. Install Client: Install the Marqo Python library:
      pip install marqo
    6. 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.py
  10. Run the Marqo Inference Orchestrator service

    mainline

    Depending 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 uvicorn to 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-inference
  11. Index documents in Marqo

    mainline

    Documents must be formatted as Python dictionaries for ingestion. Each dictionary should contain a text field and optional metadata like source. After preparing your list of documents, use the marqo.Client to 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)
  12. Run Multi-node Vespa cluster

    mainline

    To 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: m nodes, where m = number_of_shards * (1 + number_of_replicas).
    • API nodes: n nodes, where n = max(2, number_of_content_nodes).

    Example: Using --Shards 2 --Replicas 1 results in 4 content nodes and 2 API nodes.

    python vespa_local.py start --Shards 2 --Replicas 1