Home / Articles

Migrate Redis vector demos to a shared Azure AI Search index

Move LangGraph vector search from Redis demos to a shared Azure AI Search index: declare the schema, filter by session, delete by query, keep other users.

Publisher: REACTAPP.TOP6 min read1396 words

This article is published in English.

Sources: From Redis to Azure AI Search: The Full Migration

Why Redis wasn’t the final stop

Local Redis vector search is perfect for laptop demos. Shared environments, SLA-backed search, and Azure-native auth push teams toward Azure AI Search. This migration keeps the LangGraph pipeline and swaps the vector backend.

Traditional AI Search vs this pattern

Classic keyword search ranks lexical overlap. The migrated path stores embeddings beside text and retrieves by semantic similarity—optionally combined with filters. Azure AI Search becomes the managed home for those vectors.

The one decision that had to change: shared index vs per-session index

Redis demos often create a disposable index per session. Azure AI Search favors a shared index with a session (or tenant) filter field. Dropping an entire index to “clear a session” is the wrong tool; delete-by-filter is the right one.

uv add langgraph langchain-core langchain-openai langchain-text-splitters \
       azure-ai-documentintelligence azure-core \
       langchain-redis redisvl \
       python-dotenv
def get_vector_store() -> AzureSearch:
    global _vector_store
    if _vector_store is None:
        _vector_store = AzureSearch(
            azure_search_endpoint=config.AZURE_SEARCH_ENDPOINT,
            azure_search_key=config.AZURE_SEARCH_API_KEY,
            index_name=config.AZURE_SEARCH_INDEX_NAME,
            embedding_function=embeddings,
            fields=_FIELDS,
            vector_search_dimensions=config.EMBEDDING_DIMENSIONS,
        )
    return _vector_store
_FIELDS = [
    SimpleField(name="id", type=SearchFieldDataType.String, key=True, filterable=True),
    SearchableField(name="content", type=SearchFieldDataType.String),
    SearchField(
        name="content_vector",
        type="Collection(Edm.Single)",
        searchable=True,
        vector_search_dimensions=config.EMBEDDING_DIMENSIONS,
        vector_search_profile_name="myHnswProfile",
    ),
    SearchableField(name="metadata", type=SearchFieldDataType.String),
    SimpleField(name="session_id", type=SearchFieldDataType.String, filterable=True),
    SimpleField(name="file_name", type=SearchFieldDataType.String, filterable=True),
    SimpleField(name="chunk_index", type=SearchFieldDataType.Int32, filterable=True, sortable=True),
]
state["retrieved"] = vs.similarity_search(
    state["query"],
    k=config.TOP_K,
    filters=f"session_id eq '{state['session_id']}'",
)
def clear_session(session_id: str) -> None:
    vs = get_vector_store()
    results = vs.client.search(
        search_text="*",
        filter=f"session_id eq '{session_id}'",
        select=["id"],
    )
    ids = [r["id"] for r in results]
    if ids:
        vs.delete(ids=ids)
az search service create \
  --name "$SEARCH_SERVICE_NAME" \
  --resource-group "$RESOURCE_GROUP" \
  --location "$LOCATION" \
  --sku basic \
  --partition-count 1 \
  --replica-count 1

SEARCH_ADMIN_KEY=$(az search admin-key show \
  --resource-group "$RESOURCE_GROUP" \
  --service-name "$SEARCH_SERVICE_NAME" \
  --query primaryKey -o tsv)
AZURE_OPENAI_ENDPOINT=https://aif-cus-aisearchegs-dev-01.cognitiveservices.azure.com/
AZURE_OPENAI_API_KEY=<<redacted>>
# AZURE_OPENAI_API_VERSION = os.environ.get("AZURE_OPENAI_API_VERSION", "2024-10-21")
AZURE_OPENAI_EMBEDDING_DEPLOYMENT=text-embedding-3-small
AZURE_OPENAI_CHAT_DEPLOYMENT="gpt-5-mini"
# --- Azure Document Intelligence (used for all file text extraction) ---
DOCUMENTINTELLIGENCE_ENDPOINT=https://adi-cus-aisearchegs-dev-01.cognitiveservices.azure.com/
DOCUMENTINTELLIGENCE_KEY=<<redacted>>
AZURE_SEARCH_ENDPOINT=https://ais-cus-aisearchegs-dev-01.search.windows.net
AZURE_SEARCH_API_KEY=<<redacted>>
AZURE_SEARCH_INDEX_NAME=chat-file-search-index
azure.core.exceptions.HttpResponseError: The vector field 'content_vector'
must have the properties 'dimensions' and 'vectorSearchConfiguration' set.
TypeError: Object of type float32 is not JSON serializable
from azure.search.documents._utils.model_base import SdkJSONEncoder
_original_default = SdkJSONEncoder.default

def _numpy_safe_default(self, o):
    if isinstance(o, np.floating):
        return float(o)
    if isinstance(o, np.integer):
        return int(o)
    return _original_default(self, o)

SdkJSONEncoder.default = _numpy_safe_default
uv run python -m aisearchmanualvectorization.main \
  --session demo1 \
  --files ./source/sample_bank_statement.pdf ./source/sample_loan_agreement.pdf
uv run python -m aisearchmanualvectorization.main \
  --session demo1 \
  --query "What's the overdraft protection fee?"

A: The overdraft protection transfer fee is $10.00 per occurrence.

Sources:
  - sample_bank_statement.pdf (chunk 7): Overdraft Protection...
  - sample_bank_statement.pdf (chunk 8): Interest Rate & Annual Percentage Yield...
  - sample_bank_statement.pdf (chunk 9): Monthly Maintenance Fee...
  - sample_bank_statement.pdf (chunk 3): <td>-$10.44</td> <td>$8,096.52</td>...

Step by step: what changed in the code

get_vector_store() — no longer session-scoped

Return a client bound to a shared index name. Session isolation moves into metadata filters, not separate indexes.

The field schema — declared explicitly

Define vector dimensions, content fields, and filterable session IDs up front. Silent defaults that worked in Redis will not match Azure AI Search field types.

retrieve_node — a string filter instead of a filter object

Express session_id eq '…' (or the equivalent OData filter) so one user’s chunks cannot leak into another’s results.

clear_session() — delete by query, not drop-the-index

Issue a filtered delete for the session key. Recreating indexes per session will hit service limits and slow cold starts.

Migration checklist

  • Match embedding dimension to the index vector field
  • Re-embed only if the embedding model changed; otherwise re-upsert existing chunks
  • Dual-run Redis and Azure AI Search on golden questions before cutover
  • Move secrets to Key Vault / managed identity where possible
  • Update runbooks: no more “docker restart redis clears state” assumptions

Results and tradeoffs

Azure AI Search adds managed scale, portal tooling, and RBAC. It also adds provisioning lead time and the shared-index design constraint. For the PDF Q&A graph, the node graph stayed stable; only the store adapter and session lifecycle changed.

Closing

Treat Redis as the teaching wheels and Azure AI Search as the road tires. Keep session isolation in filters, declare schema explicitly, and delete by query. The LangGraph orchestration should barely notice—if it does, the abstraction was too leaky.

Cutover tip: keep Redis read-only for a week while Azure AI Search serves production traffic, comparing top-3 overlap on the golden question set. If overlap drops, inspect filter clauses and field names before blaming the embedding model. Most migration regressions in this pattern are filter bugs, not vector math bugs.

Cutover tip: keep Redis read-only for a week while Azure AI Search serves production traffic, comparing top-3 overlap on the golden question set. If overlap drops, inspect filter clauses and field names before blaming the embedding model. Most migration regressions in this pattern are filter bugs, not vector math bugs.

Cutover tip: keep Redis read-only for a week while Azure AI Search serves production traffic, comparing top-3 overlap on the golden question set. If overlap drops, inspect filter clauses and field names before blaming the embedding model. Most migration regressions in this pattern are filter bugs, not vector math bugs.

Cutover tip: keep Redis read-only for a week while Azure AI Search serves production traffic, comparing top-3 overlap on the golden question set. If overlap drops, inspect filter clauses and field names before blaming the embedding model. Most migration regressions in this pattern are filter bugs, not vector math bugs.

Cutover tip: keep Redis read-only for a week while Azure AI Search serves production traffic, comparing top-3 overlap on the golden question set. If overlap drops, inspect filter clauses and field names before blaming the embedding model. Most migration regressions in this pattern are filter bugs, not vector math bugs.

Cutover tip: keep Redis read-only for a week while Azure AI Search serves production traffic, comparing top-3 overlap on the golden question set. If overlap drops, inspect filter clauses and field names before blaming the embedding model. Most migration regressions in this pattern are filter bugs, not vector math bugs.

Cutover tip: keep Redis read-only for a week while Azure AI Search serves production traffic, comparing top-3 overlap on the golden question set. If overlap drops, inspect filter clauses and field names before blaming the embedding model. Most migration regressions in this pattern are filter bugs, not vector math bugs.

Cutover tip: keep Redis read-only for a week while Azure AI Search serves production traffic, comparing top-3 overlap on the golden question set. If overlap drops, inspect filter clauses and field names before blaming the embedding model. Most migration regressions in this pattern are filter bugs, not vector math bugs.

Cutover tip: keep Redis read-only for a week while Azure AI Search serves production traffic, comparing top-3 overlap on the golden question set. If overlap drops, inspect filter clauses and field names before blaming the embedding model. Most migration regressions in this pattern are filter bugs, not vector math bugs.

Cutover tip: keep Redis read-only for a week while Azure AI Search serves production traffic, comparing top-3 overlap on the golden question set. If overlap drops, inspect filter clauses and field names before blaming the embedding model. Most migration regressions in this pattern are filter bugs, not vector math bugs.

Cutover tip: keep Redis read-only for a week while Azure AI Search serves production traffic, comparing top-3 overlap on the golden question set. If overlap drops, inspect filter clauses and field names before blaming the embedding model. Most migration regressions in this pattern are filter bugs, not vector math bugs.

Schema discipline matters twice: once when creating the Azure AI Search index, and again when the embedding model is upgraded. Dimension mismatches fail loudly; filterable field mismatches fail quietly as empty result sets. Add a startup assertion that reads the index definition and compares vector dimensions and session field names to the running config.