Skip to content

3.3 Retrieve Related Products

1. Add Docs Retrieval Script

Let's copy over the get_product_documents.py script into our application source folder.

1
cp src.sample/api/get_product_documents.py  src/api/.

2. Understand Docs Retrieval

Let's start with a sample user query like this:

1
 I need a new tent for 4 people, what would you recommend?

Different users could phrase the question in different ways, with different levels of information. But we need to map all these queries to a search query that works on the product database. How do we do that? We use a 3-step process:

  1. We teach an AI to extract user intent from an input text query
  2. We teach the AI to map user intent to a search query on products
  3. We use the search index to retrieve product documents matching query.

Let's see how we do this.


3. Create AI Project Client

  1. Create an Azure AI Project client using the connection string
  2. Use the client to retrieve a chat_completions inference client
  3. Use the client to retrieve an embeddings inference client
  4. Use the client to setup a search_client using the search connection
Click to expand and view the Python script
src/api/get_product_documents.py - Part 1
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import os
from pathlib import Path
from opentelemetry import trace
from azure.ai.projects import AIProjectClient
from azure.ai.projects.models import ConnectionType
from azure.identity import DefaultAzureCredential
from azure.core.credentials import AzureKeyCredential
from azure.search.documents import SearchClient
from config import ASSET_PATH, get_logger

# initialize logging and tracing objects
logger = get_logger(__name__)
tracer = trace.get_tracer(__name__)

# create a project client using environment variables loaded from the .env file
project = AIProjectClient.from_connection_string(
    conn_str=os.environ["AIPROJECT_CONNECTION_STRING"], credential=DefaultAzureCredential()
)

# create a vector embeddings client that will be used to generate vector embeddings
chat = project.inference.get_chat_completions_client()
embeddings = project.inference.get_embeddings_client()

# use the project client to get the default search connection
search_connection = project.connections.get_default(
    connection_type=ConnectionType.AZURE_AI_SEARCH, include_credentials=True
)

# Create a search index client using the search connection
# This client will be used to create and delete search indexes
search_client = SearchClient(
    index_name=os.environ["AISEARCH_INDEX_NAME"],
    endpoint=search_connection.endpoint_url,
    credential=AzureKeyCredential(key=search_connection.key),
)

4. Get Docs For User Intent

  1. First, receive input text string (user query)
  2. Then, map user query text into a clear intent (search query)
  3. Then, vectorize the search query (to support retrieval)
  4. Then, search the product index for matches (by cosine similarity)
  5. Then, for each matching product, retrieve its document (content)
  6. Return the collection of documents to the user.
Click to expand and view the Python script
src/api/get_product_documents.py - Part 2
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
    from azure.ai.inference.prompts import PromptTemplate
    from azure.search.documents.models import VectorizedQuery

    @tracer.start_as_current_span(name="get_product_documents")
    def get_product_documents(messages: list, context: dict = None) -> dict:
        if context is None:
            context = {}

        overrides = context.get("overrides", {})
        top = overrides.get("top", 5)

        # generate a search query from the chat messages
        intent_prompty = PromptTemplate.from_prompty(Path(ASSET_PATH) / "intent_mapping.prompty")

        intent_mapping_response = chat.complete(
            model=os.environ["INTENT_MAPPING_MODEL"],
            messages=intent_prompty.create_messages(conversation=messages),
            **intent_prompty.parameters,
        )

        search_query = intent_mapping_response.choices[0].message.content
        logger.debug(f"🧠 Intent mapping: {search_query}")

        # generate a vector representation of the search query
        embedding = embeddings.embed(model=os.environ["EMBEDDINGS_MODEL"], input=search_query)
        search_vector = embedding.data[0].embedding

        # search the index for products matching the search query
        vector_query = VectorizedQuery(vector=search_vector, k_nearest_neighbors=top, fields="contentVector")

        search_results = search_client.search(
            search_text=search_query, vector_queries=[vector_query], select=["id", "content", "filepath", "title", "url"]
        )

        documents = [
            {
                "id": result["id"],
                "content": result["content"],
                "filepath": result["filepath"],
                "title": result["title"],
                "url": result["url"],
            }
            for result in search_results
        ]

        # add results to the provided context
        if "thoughts" not in context:
            context["thoughts"] = []

        # add thoughts and documents to the context object so it can be returned to the caller
        context["thoughts"].append(
            {
                "title": "Generated search query",
                "description": search_query,
            }
        )

        if "grounding_data" not in context:
            context["grounding_data"] = []
        context["grounding_data"].append(documents)

        logger.debug(f"📄 {len(documents)} documents retrieved: {documents}")
        return documents

5. Run Docs Retrieval Script

Before we can run this script, we need to create the Intent Mapper template for step 2. Let's do that next.