Azure AI Search with vectorized search

 

We can create a vectorized search for our Azure AI Search without going through setting up Azure Foundry embedding model. We will just use standard embedding "hnsw" and we also don't require a indexer for now, probably when we have more document to index. 

The setup process would be 

1. Create index

2. Embed and upload your docs - we require this to show how we can vectorized our document so we can test it out later

3. Perform vector search

Creating index

We can create our vector index called "index-vector" using the following code. As you can see here, we are also embedding and uploading the document 




from azure.identity import DefaultAzureCredential
from azure.core.credentials import AzureKeyCredential
from azure.search.documents.indexes import SearchIndexClient
from azure.search.documents import SearchClient
from azure.search.documents.indexes.models import (
    ComplexField,
    SimpleField,
    SearchFieldDataType,
    SearchableField,
    SearchIndex,
    SearchField,
    VectorSearch,
    HnswAlgorithmConfiguration,
    VectorSearchProfile,
)

from azure.search.documents import SearchClient
from sentence_transformers import SentenceTransformer
from azure.search.documents.models import VectorizedQuery

credential = DefaultAzureCredential()
index_name: str = "index-vector"
search_endpoint: str = "https://your-search-endpoint.search.windows.net"

index_client = SearchIndexClient(
    endpoint=search_endpoint, credential=credential)

api_key = "your-api-key"


# Delete old index
try:
    index_client.delete_index(index_name)
    print(f"Deleted old index '{index_name}'")
except:
    pass

# Create new index with retrievable embedding field
fields = [
    SimpleField(name="id", type=SearchFieldDataType.String, key=True),
    SearchableField(name="text", type=SearchFieldDataType.String, retrievable=True),
    SearchableField(name="source", type=SearchFieldDataType.String, retrievable=True),
    SearchField(
        name="embedding",
        type=SearchFieldDataType.Collection(SearchFieldDataType.Single),
        searchable=True,
        retrievable=True,  # KEY: Make embedding retrievable
        vector_search_dimensions=384,
        vector_search_profile_name="myHnsw",
    ),
]

vector_search_config = VectorSearch(
    algorithms=[HnswAlgorithmConfiguration(name="myHnsw")],
    profiles=[VectorSearchProfile(name="myHnsw", algorithm_configuration_name="myHnsw")],
)

index = SearchIndex(
    name=index_name,
    fields=fields,
    vector_search=vector_search_config,
)

index_client.create_index(index)
print(f"✓ Created index '{index_name}' with retrievable embedding field")

# Now re-upload the hotel documents
from sentence_transformers import SentenceTransformer

model = SentenceTransformer("all-MiniLM-L6-v2")

sample_documents = [
    {
        "id": "hotel-001",
        "text": "Sunset Beach Resort is a luxury 5-star hotel located in Miami Beach with oceanfront views. Features include a spa, fine dining restaurants, and a private beach access.",
        "source": "hotel-booking-miami.txt"
    },
    {
        "id": "hotel-002",
        "text": "Mountain View Lodge is a cozy 3-star hotel nestled in the Rocky Mountains near Denver. Perfect for hiking enthusiasts.",
        "source": "hotel-booking-denver.txt"
    },
    {
        "id": "hotel-003",
        "text": "Downtown Urban Hotel is a business-oriented 4-star property in the heart of New York City.",
        "source": "hotel-booking-nyc.txt"
    },
    {
        "id": "hotel-004",
        "text": "Tropical Paradise Resort in Cancun offers all-inclusive packages with beachfront bungalows and water sports.",
        "source": "hotel-booking-cancun.txt"
    },
    {
        "id": "hotel-005",
        "text": "Historic Inn and Spa in Charleston features charming colonial architecture and luxury spa treatments.",
        "source": "hotel-booking-charleston.txt"
    }
]

documents_to_update = []
for doc in sample_documents:
    doc_with_embedding = {
        "id": doc["id"],
        "text": doc["text"],
        "source": doc["source"],
        "embedding": model.encode(doc["text"]).tolist()
    }
    documents_to_update.append(doc_with_embedding)


from azure.search.documents import SearchClient
search_client = SearchClient(
    endpoint=search_endpoint,
    credential=AzureKeyCredential(api_key),
    index_name=index_name
)

search_client.upload_documents(documents_to_update)
print(f"✓ Uploaded {len(documents_to_update)} hotel documents\n")



You can see that we have 5 document added into our index.




Then we will setup our search 

from azure.identity import DefaultAzureCredential
from azure.core.credentials import AzureKeyCredential
from azure.search.documents.indexes import SearchIndexClient, SearchIndexerClient
from azure.search.documents import SearchClient
from azure.search.documents.indexes.models import (
    ComplexField,
    SimpleField,
    SearchFieldDataType,
    SearchableField,
    SearchIndex,
    SearchField,
    VectorSearch,
    HnswAlgorithmConfiguration,
    VectorSearchProfile,
)

from azure.search.documents import SearchClient
from sentence_transformers import SentenceTransformer
from azure.search.documents.models import VectorizedQuery

credential = DefaultAzureCredential()

search_endpoint: str = "https://your-search-endpoint.search.windows.net"
api_key = "your-api-key"  # Replace with your actual API key
# Load the embedding model (produces 384-dimensional vectors)
model = SentenceTransformer("all-MiniLM-L6-v2")
# Get status of a specific indexer
index_name: str = "index-vector"

index_client = SearchIndexClient(
    endpoint=search_endpoint, credential=credential)

indexes = index_client.list_index_names()
print(f"Available indexes: {list(indexes)}")

# Check how many documents are actually in your index
from azure.search.documents import SearchClient
search_client = SearchClient(
    endpoint=search_endpoint,
    credential=AzureKeyCredential(api_key),
    index_name=index_name
)

# Count documents in index
results = search_client.search(search_text="*", select=["id"])
doc_count = sum(1 for _ in results)
print(f"\nDocuments in index: {doc_count}")

# setup index search
search_client = SearchClient(
    endpoint=search_endpoint,
    credential=AzureKeyCredential(api_key),
    index_name=index_name
)

def search_vector(question, k=3):
    """Pure vector search"""
    query_vector = model.encode(question)
    vector_query = VectorizedQuery(
        vector=query_vector.tolist(),
        k_nearest_neighbors=k,
        fields="embedding",
    )
    results = search_client.search(
        search_text=None,
        vector_queries=[vector_query],
        select=["id", "text", "source"],  # Fixed field names
    )
    return results

def search_hybrid(question, k=3):
    query_vector = model.encode(question)
    vector_query = VectorizedQuery(
        vector=query_vector.tolist(),
        k_nearest_neighbors=k,
        fields="embedding",
    )
    results = search_client.search(
        search_text=question,  # Keyword search too
        vector_queries=[vector_query],
        select=["id", "text", "source"],
        top=k,
    )
    return results


# Perform search works!
search_term = "hotel"
print(f"=== Hybrid Search Results for '{search_term}' ===")

query_vector = model.encode(search_term)
from azure.search.documents.models import VectorizedQuery
vector_query = VectorizedQuery(
    vector=query_vector.tolist(),
    k_nearest_neighbors=5,
    fields="embedding",
)

results = search_client.search(
    search_text=search_term,
    vector_queries=[vector_query],
    select=["id", "text", "source", "embedding"],
    top=5,
)

for result in results:
    print(f"\nScore: {result['@search.score']:.4f}")
    print(f"Source: {result['source']}")
    print(f"Text: {result['text'][:100]}...")



And let's test it out by passing in and not so relevant text like "unknownforsure" and you can see the score is pretty low


And when we search for "hotel", our score become higher:-


Uploading: 70967 of 70967 bytes uploaded.


This is what our index vector in json looks like.

{

  "@odata.etag": "\"0x8DF06160AB01F08\"",
  "name": "index-vector",
  "purviewEnabled": false,
  "fields": [
    {
      "name": "id",
      "type": "Edm.String",
      "searchable": false,
      "filterable": false,
      "retrievable": true,
      "stored": true,
      "sortable": false,
      "facetable": false,
      "key": true,
      "synonymMaps": []
    },
    {
      "name": "text",
      "type": "Edm.String",
      "searchable": true,
      "filterable": false,
      "retrievable": true,
      "stored": true,
      "sortable": false,
      "facetable": false,
      "key": false,
      "synonymMaps": []
    },
    {
      "name": "source",
      "type": "Edm.String",
      "searchable": true,
      "filterable": false,
      "retrievable": true,
      "stored": true,
      "sortable": false,
      "facetable": false,
      "key": false,
      "synonymMaps": []
    },
    {
      "name": "embedding",
      "type": "Collection(Edm.Single)",
      "searchable": true,
      "filterable": false,
      "retrievable": true,
      "stored": true,
      "sortable": false,
      "facetable": false,
      "key": false,
      "dimensions": 384,
      "vectorSearchProfile": "myHnsw",
      "synonymMaps": []
    }
  ],
  "scoringProfiles": [],
  "suggesters": [],
  "analyzers": [],
  "normalizers": [],
  "tokenizers": [],
  "tokenFilters": [],
  "charFilters": [],
  "similarity": {
    "@odata.type": "#Microsoft.Azure.Search.BM25Similarity"
  },
  "vectorSearch": {
    "algorithms": [
      {
        "name": "myHnsw",
        "kind": "hnsw",
        "hnswParameters": {
          "metric": "cosine",
          "m": 4,
          "efConstruction": 400,
          "efSearch": 500
        }
      }
    ],
    "profiles": [
      {
        "name": "myHnsw",
        "algorithm": "myHnsw"
      }
    ],
    "vectorizers": [],
    "compressions": []
  }
}



Comments

Popular posts from this blog

Windows SSH: Permissions for 'private-key' are too open

NodeJS: Error: spawn EINVAL in window for node version 20.20 and 18.20