Skip to content

Personalised Search and Recommendations

Search and recommendations are largerly similar tasks. Vector search enables real time personalisation of both by injecting additional query terms into the search.

A note on implementation: Typically the desired effect (a slight tailoring of results) is best achieved when the additional context you inject is diverse with regards to your data. For example if you have a broad range of documents (like Amazon) then you want to inject a range of items that the user has shown interest in rather than only t-shirts. Conversly if all you have is t-shirts then diversity can come in the form of patterns and styles. Without diversity search and recommendations may steer away from the relevant results and towards to known priors.

Personalisation with Query Terms

Where additional text or images are known, these can use used to condition any subsequent search or recommendation. This is useful for personalisation based on user history, profiles, images, free text context, or other unstuctured sources of contextual information.

We aim to build a multi-term query with contextual information contributing a low enough weight to not overpower the original query. We will us the weighted multi-term queries support in marqo, some more info on best practices for these can be found here.

A simple example of code to do this is as follows:

import marqo

mq = marqo.Client()

personalisation_context = [
    "I love bold patterns and bright colours",  # free text about a user
    "It's the 60s! Pattern living room rug",  # a product title they interacted with
    "https://example.com/image.jpg",  # a product image they interacted with
    # etc
]

# something sufficiently low to not overpower the original query
# will most likely require tuning
total_personalisation_weight = 0.2

# the actual query for what the user wants
query = "t-shirt"


def personalise_query(query, personalisation_context, total_personalisation_weight):
    personalisation_weight = total_personalisation_weight / len(personalisation_context)

    composed_query = {}

    for context in personalisation_context:
        composed_query[context] = personalisation_weight

    composed_query[query] = 1.0

    return composed_query


mq.index("my-first-index").search(
    q=personalise_query(query, personalisation_context, total_personalisation_weight)
)

Personalisation with Existing Documents

The same principle can be applied to personalisation with existing documents. This is useful for personalisation based on user interaction history, current checkout cart, or historical purchases.

The key difference is that we will pull the vectors out of Marqo and use them to condition the search or recommendation.

import marqo
from typing import List, Dict

mq = marqo.Client()

personalisation_context = [
    "document1",  # a document id
    "document2",  # another document id
    # etc
]

# something sufficiently low to not overpower the original query
# will most likely require tuning
total_personalisation_weight = 0.2

# the actual query for what the user wants
query = "t-shirt"


def get_personalisation_vectors(
    mq: marqo.Client,
    index_name: str,
    personalisation_context: List[str],
    total_personalisation_weight: float,
) -> Dict[str, List[Dict[str, float]]]:
    personalisation_weight = total_personalisation_weight / len(personalisation_context)

    items_with_facets = mq.index(index_name).get_documents(
        document_ids=personalisation_context, expose_facets=True
    )

    vecs = []

    for item_with_facets in items_with_facets["results"]:
        try:
            for facet in item_with_facets["_tensor_facets"]:
                vecs.append(facet["_embedding"])
        except KeyError:
            pass

    context_tensor = []

    for vec in vecs:
        context_tensor.append({"vector": vec, "weight": personalisation_weight})

    return {"tensor": context_tensor}


mq.index("my-first-index").search(
    q={query: 1.0},
    context=get_personalisation_vectors(
        mq, "my-first-index", personalisation_context, total_personalisation_weight
    ),
)