feat(search): migrate Azure AI Search to the 2026-04-01 API and profile-based vector schema - #2604
Conversation
|
Azure Pipelines: There may be pipelines that require an authorized user to comment /azp run to run. |
|
Hey Rana Singh (@ranadeepsingh) 👋! We use semantic commit messages to streamline the release process. Examples of commit messages with semantic prefixes:
To test your commit locally, please follow our guild on building from source. |
There was a problem hiding this comment.
Pull request overview
Updates SynapseML’s Azure AI Search integration to default to the 2026-04-01 data-plane API while preserving compatibility across the legacy (pre-2023-11-01) and modern vector index schema shapes by translating index definitions to the schema required by the selected api-version.
Changes:
- Switch default Azure AI Search API version to
2026-04-01and add anapi-versiongate for profile-based vector schemas. - Introduce vector schema alignment (legacy ⇄ modern), including dual parsing and a unified
vectorReferenceaccessor. - Update writer behavior so document upload uses the same resolved
api-versionas index operations; refresh docs/notebooks to “Azure AI Search” and current sample API versions.
Show a summary per file
| File | Description |
|---|---|
| docs/Quick Examples/transformers/cognitive/_AzureSearch.md | Renames service heading to Azure AI Search. |
| docs/Explore Algorithms/OpenAI/Quickstart - Understand and Search Forms.ipynb | Updates narrative + sample REST api-version to 2026-04-01. |
| docs/Explore Algorithms/AI Services/Quickstart - Document Question and Answering with PDFs.ipynb | Updates naming + vector-store REST api-version to 2026-04-01. |
| docs/Explore Algorithms/AI Services/Quickstart - Create a Visual Search Engine.ipynb | Updates naming + sample REST api-version to 2026-04-01. |
| docs/Explore Algorithms/AI Services/Overview.ipynb | Updates Azure AI Search naming in overview and comments. |
| cognitive/src/test/scala/com/microsoft/azure/synapse/ml/services/search/VectorSchemaMigrationSuite.scala | Adds secret-free tests validating schema migration + version gate behavior. |
| cognitive/src/test/scala/com/microsoft/azure/synapse/ml/services/search/split2/SearchWriterSuitePart2.scala | Uses version-agnostic vectorReference assertion for vectors. |
| cognitive/src/test/scala/com/microsoft/azure/synapse/ml/services/search/split1/SearchWriterSuitePart1.scala | Uses version-agnostic vectorReference assertions for vectors. |
| cognitive/src/main/scala/com/microsoft/azure/synapse/ml/services/search/AzureSearchSchemas.scala | Adds modern vector schema types (profiles/algorithms), vectorReference, and VectorSchema.align. |
| cognitive/src/main/scala/com/microsoft/azure/synapse/ml/services/search/AzureSearchAuth.scala | Updates user-facing auth error strings to “Azure AI Search”. |
| cognitive/src/main/scala/com/microsoft/azure/synapse/ml/services/search/AzureSearchAPI.scala | Defaults api-version to 2026-04-01; aligns schema pre-validation; updates vector validation to use vectorReference. |
| cognitive/src/main/scala/com/microsoft/azure/synapse/ml/services/search/AzureSearch.scala | Emits modern vector schema by default; pins AddDocuments endpoint to the selected api-version; switches vector detection to isVectorField. |
Review details
Suppressed comments (1)
cognitive/src/main/scala/com/microsoft/azure/synapse/ml/services/search/AzureSearchAPI.scala:160
validateIndexInfoaligns vector schemas but still allows vector fields to be sent without anyvectorSearch.algorithms/ legacyvectorSearch.algorithmConfigurationsbeing defined (e.g., if a user supplies dimensions + vectorSearchProfile/config on fields but omits the vectorSearch section). That will pass current validation and fail later at request time with a less actionable service error. Consider rejecting this early with an explicit validation check after alignment.
private def validateIndexInfo(indexJson: String, apiVersion: String): Try[IndexInfo] = {
// Align first so the definition that is validated is exactly the one that gets sent.
val schema = VectorSchema.align(parseIndexJson(indexJson), apiVersion)
for {
_ <- validName(schema.name.get)
_ <- validIndexFields(schema.fields)
} yield schema
}
- Files reviewed: 12/12 changed files
- Comments generated: 0
- Review effort level: Lite
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #2604 +/- ##
==========================================
- Coverage 86.33% 86.21% -0.12%
==========================================
Files 335 335
Lines 18007 18178 +171
Branches 1658 1706 +48
==========================================
+ Hits 15546 15673 +127
- Misses 2461 2505 +44 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
## Summary Preserve published Azure Search schema APIs while modernizing REST payloads losslessly, enforce the correct profile-schema version boundary, validate actual remote indexes, and update the 2026 vector-query sample. ## Prompting Intent Rebase PR microsoft#2604 onto current master and resolve all review findings without destructive migrations or avoidable network calls. Keep public JVM/source behavior compatible, preserve unknown service fields, use the selected API version consistently, and retain a lean and performant Search writer path. ## Linked Sources - Pull request: microsoft#2604 - Authentication precursor: microsoft#2591 - Azure AI Search API migration guidance: https://learn.microsoft.com/azure/search/search-api-migration - Create or Update Index API: https://learn.microsoft.com/rest/api/searchservice/indexes/create-or-update ## Rationale Public case-class shapes remain unchanged while JSON-AST transforms rename only schema-generation keys, preserving tuning, vectorizer, compression, and future fields. Existing legacy indexes fail with actionable guidance rather than being silently ignored or destructively rewritten. Explicit date parsing avoids lexical version bugs, and direct index creation validates the fetched remote schema with one list and one GET whose responses are always closed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 81d39bfc-927c-418a-90a8-e0f2cd8fc128
e161131 to
92d7f41
Compare
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
There was a problem hiding this comment.
Review details
Suppressed comments (2)
cognitive/src/main/scala/com/microsoft/azure/synapse/ml/services/search/AzureSearch.scala:368
- When the index does not exist,
prepareDFcallsgetExisting(...)and then callsSearchIndex.createIfNoneExists(...), which performs its ownclient.getExisting(...)list call. This results in two index LIST requests on the create path (extra latency + rate-limit pressure). Consider refactoring so the create path reuses the already-fetched index list (e.g., call the private[search]createIfNoneExists(..., client)overload with a client that returns the cached list).
val existingIndexJsonOpt = if (getExisting(auth, serviceName, apiVersion).contains(indexName)) {
if (indexJsonOpt.isDefined) {
println(f"indexJsonOpt is specified, however an index for $indexName already exists," +
f"we will use the index definition obtained from the existing index instead")
}
val existingIndexJson = IndexJsonReader.get(auth, serviceName, indexName, apiVersion)
VectorSchema.requireCompatibleExistingIndex(existingIndexJson.parseJson, apiVersion)
Some(existingIndexJson)
} else {
None
}
val (indexJson, preppedDF) = resolveIndexDefinition(
existingIndexJsonOpt, indexJsonOpt, vectorColsInfo, df, indexName, keyCol, actionCol)
// TODO: Support vector search in nested fields
// Throws an exception if any nested field is a vector in the schema
parseIndexJson(indexJson).fields.foreach(_.fields.foreach(assertNoNestedVectors))
if (existingIndexJsonOpt.isEmpty) {
SearchIndex.createIfNoneExists(auth, serviceName, indexJson, apiVersion)
}
cognitive/src/main/scala/com/microsoft/azure/synapse/ml/services/search/AzureSearch.scala:347
apiVersionis used ingetExisting(...)before it is validated. If a caller supplies an invalid apiVersion, this will attempt a network request and only fail later, even thoughsupportsVectorProfilesis intended to reject invalid versions before sending requests. ValidateapiVersionright before the first request.
val existingIndexJsonOpt = if (getExisting(auth, serviceName, apiVersion).contains(indexName)) {
- Files reviewed: 12/12 changed files
- Comments generated: 0 new
- Review effort level: Lite
## Summary Teach the release-branch compatibility job to apply explicitly declared, release-relevant prerequisite commits before replaying the current PR, and cover the behavior with an executable isolated Git test. ## Prompting Intent Prepare PR microsoft#2604 for merge after its Spark 4.1 validation exposed a dependency on the already-merged Azure AI Search authentication precursor. Keep the fix generic, deterministic, safe against arbitrary commit injection, and inexpensive for future release compatibility checks. ## Linked Sources - Pull request: microsoft#2604 - Authentication precursor: microsoft#2591 - Spark 4.1 compatibility branch: https://github.com/microsoft/SynapseML/tree/spark4.1 ## Rationale A small ordered manifest avoids hard-coding PR-specific logic into the pipeline. Each full SHA must be an ancestor of the PR target, only release-relevant paths are replayed, and reverse application detects prerequisites already present on a release branch. Applying staged prerequisite patches before the target-relative PR patch preserves the exact compatibility scenario while avoiding rebases, network API lookups, and unrelated infrastructure changes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 81d39bfc-927c-418a-90a8-e0f2cd8fc128
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
## Summary Detect legacy vector indexes whose modern API representation omits profile references, and keep the shared document-QA sample on the explicit legacy write path until that index is migrated. ## Prompting Intent Audit and repair PR microsoft#2604 after Azure validation exposed a real Databricks notebook regression. Preserve the PR's non-destructive migration contract, keep new indexes on the 2026 API default, retain VectorUDT upload compatibility for the existing shared sample, and add focused regression coverage. ## Linked Sources - Pull request: microsoft#2604 - Failed Azure build: https://msdata.visualstudio.com/_build/results?buildId=230161909 - Azure AI Search API migration guidance: https://learn.microsoft.com/azure/search/search-api-migration - Create or Update Index API: https://learn.microsoft.com/rest/api/searchservice/indexes/create-or-update ## Rationale Azure AI Search normalizes a legacy index queried through a modern API into algorithms plus a vector field with dimensions but a null profile. Detecting that semantic shape preserves the actionable migration error instead of reaching an unrelated schema-parity assertion. Pinning only the shared legacy sample to 2023-07-01-Preview avoids deleting or silently rewriting the persistent index, while newly created indexes continue to use the modern default. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 81d39bfc-927c-418a-90a8-e0f2cd8fc128
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
There was a problem hiding this comment.
Review details
Suppressed comments (2)
tools/ci/tests/test_pipeline_yaml.py:360
- This test is named as a format/validation check, but it hard-codes the exact prerequisite SHA list. That will cause unrelated breakages whenever the prerequisites file is updated (e.g., when the backport lands or additional prerequisites are added). Prefer validating that prerequisites are present, unique, and full 40-character SHAs.
assert lines == ["04897bae9baa08f0d67855566f7bad235791d508"]
assert all(re.fullmatch(r"[0-9a-fA-F]{40}", line) for line in lines)
cognitive/src/main/scala/com/microsoft/azure/synapse/ml/services/search/AzureSearchSchemas.scala:284
- In
rewriteField, the thrown error says it cannot create a vector profile for an algorithm name, but this code path also handles modernvectorSearchProfilevalues (which are profile names). The message is misleading and makes debugging invalid modern schemas harder.
val reference = selectAlias(fields, ModernFieldReferenceKey, LegacyFieldReferenceKey).map {
case JsString(name) =>
JsString(references.getOrElse(name, throw new IllegalArgumentException(
s"Cannot find or create a vector profile for algorithm '$name'")))
- Files reviewed: 15/15 changed files
- Comments generated: 0 new
- Review effort level: Lite
… vector schema SynapseML pinned `2023-07-01-Preview`, the only preview data-plane version Microsoft explicitly tags "(deprecated)" — deprecated 2024-04-08 and out of support since 2024-07-08. This moves the default to the current stable version, `2026-04-01`. `2023-11-01` introduced a breaking change to the vector index schema that SynapseML never adopted, so a naive version bump would break every vector index: legacy (<= 2023-10-01-preview) modern (>= 2023-11-01) vectorSearch.algorithmConfigurations -> vectorSearch.algorithms + .profiles field.vectorSearchConfiguration -> field.vectorSearchProfile searchable not allowed on a vector -> searchable: true is required Rather than a hard cutover, `VectorSchema.align` translates a parsed index definition in both directions to whatever shape the requested api-version expects, and runs before validation so what is validated is exactly what is sent. Users who pass legacy `indexJson`, and users who pin an older `apiVersion`, both keep working. When upgrading legacy JSON each algorithm config is mirrored as a same-named profile, so pre-existing `vectorSearchConfiguration: "vectorConfig"` field references resolve unchanged. `getVectorColConf` reads back either spelling. Also fixes a pre-existing inconsistency where `AddDocuments` hard-coded the default api-version in its document endpoint, so an explicit `apiVersion` option applied to index creation but silently not to document upload. Public class names are deliberately unchanged; renaming `AzureSearch` / `AzureSearchWriter` / `AddDocuments` would be a breaking API change. Adds VectorSchemaMigrationSuite: 10 credential-free tests covering the version gate, both translation directions, idempotency, round-tripping, dual parsing and nested fields. Verified with cognitive/Test/compile, scalastyle, Test/scalastyle (0 errors) and 65/65 passing search tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 06ab581d-5e25-497c-a1b5-95f94ed63b21
…fresh api-versions The service was renamed to Azure AI Search; SynapseML docs still called it Azure Cognitive Search. Sample `apiVersion` values were also stale (`2019-05-06` and the deprecated `2023-07-01-Preview`) and are now `2026-04-01`, matching the new default. Only prose and sample values change — the `AzureSearchWriter` / `AzureSearch` API names are untouched. `website/versioned_docs/**` is intentionally left alone since those are frozen release snapshots. All edited notebooks re-validated as parseable. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 06ab581d-5e25-497c-a1b5-95f94ed63b21
## Summary Preserve published Azure Search schema APIs while modernizing REST payloads losslessly, enforce the correct profile-schema version boundary, validate actual remote indexes, and update the 2026 vector-query sample. ## Prompting Intent Rebase PR microsoft#2604 onto current master and resolve all review findings without destructive migrations or avoidable network calls. Keep public JVM/source behavior compatible, preserve unknown service fields, use the selected API version consistently, and retain a lean and performant Search writer path. ## Linked Sources - Pull request: microsoft#2604 - Authentication precursor: microsoft#2591 - Azure AI Search API migration guidance: https://learn.microsoft.com/azure/search/search-api-migration - Create or Update Index API: https://learn.microsoft.com/rest/api/searchservice/indexes/create-or-update ## Rationale Public case-class shapes remain unchanged while JSON-AST transforms rename only schema-generation keys, preserving tuning, vectorizer, compression, and future fields. Existing legacy indexes fail with actionable guidance rather than being silently ignored or destructively rewritten. Explicit date parsing avoids lexical version bugs, and direct index creation validates the fetched remote schema with one list and one GET whose responses are always closed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 81d39bfc-927c-418a-90a8-e0f2cd8fc128
Teach the release-branch compatibility job to apply explicitly declared, release-relevant prerequisite commits before replaying the current PR, and cover the behavior with an executable isolated Git test. Prepare PR microsoft#2604 for merge after its Spark 4.1 validation exposed a dependency on the already-merged Azure AI Search authentication precursor. Keep the fix generic, deterministic, safe against arbitrary commit injection, and inexpensive for future release compatibility checks. - Pull request: microsoft#2604 - Authentication precursor: microsoft#2591 - Spark 4.1 compatibility branch: https://github.com/microsoft/SynapseML/tree/spark4.1 A small ordered manifest avoids hard-coding PR-specific logic into the pipeline. Each full SHA must be an ancestor of the PR target, only release-relevant paths are replayed, and reverse application detects prerequisites already present on a release branch. Applying staged prerequisite patches before the target-relative PR patch preserves the exact compatibility scenario while avoiding rebases, network API lookups, and unrelated infrastructure changes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 81d39bfc-927c-418a-90a8-e0f2cd8fc128
## Summary Detect legacy vector indexes whose modern API representation omits profile references, and keep the shared document-QA sample on the explicit legacy write path until that index is migrated. ## Prompting Intent Audit and repair PR microsoft#2604 after Azure validation exposed a real Databricks notebook regression. Preserve the PR's non-destructive migration contract, keep new indexes on the 2026 API default, retain VectorUDT upload compatibility for the existing shared sample, and add focused regression coverage. ## Linked Sources - Pull request: microsoft#2604 - Failed Azure build: https://msdata.visualstudio.com/_build/results?buildId=230161909 - Azure AI Search API migration guidance: https://learn.microsoft.com/azure/search/search-api-migration - Create or Update Index API: https://learn.microsoft.com/rest/api/searchservice/indexes/create-or-update ## Rationale Azure AI Search normalizes a legacy index queried through a modern API into algorithms plus a vector field with dimensions but a null profile. Detecting that semantic shape preserves the actionable migration error instead of reaching an unrelated schema-parity assertion. Pinning only the shared legacy sample to 2023-07-01-Preview avoids deleting or silently rewriting the persistent index, while newly created indexes continue to use the modern default. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 81d39bfc-927c-418a-90a8-e0f2cd8fc128
|
/azp run |
28a7cfe to
bd75e92
Compare
|
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command. |
There was a problem hiding this comment.
Review details
Suppressed comments (2)
cognitive/src/main/scala/com/microsoft/azure/synapse/ml/services/search/AzureSearchSchemas.scala:241
modernizeVectorSearchonly auto-generatesvectorSearch.profileswhenupgradingLegacy(i.e., when onlyalgorithmConfigurationsis present). If a caller supplies a mixed schema (e.g., already-renamedvectorSearch.algorithmsbut still noprofiles),rewriteFieldwill fail with "Cannot find or create a vector profile..." even though default profiles can be generated losslessly from the algorithms list. Consider generating default profiles whenever profiles are absent, not only when upgrading legacy keys.
val generatedProfiles = if (upgradingLegacy) {
algorithms.toSeq.flatMap(asArray(_, ModernAlgorithmsKey)).flatMap { algorithm =>
val algorithmName = objectString(algorithm, "name", ModernAlgorithmsKey)
if (profileByAlgorithm.contains(algorithmName)) {
None
cognitive/src/main/scala/com/microsoft/azure/synapse/ml/services/search/AzureSearch.scala:351
prepareDFcallsgetExisting(auth, serviceName, apiVersion)to determine whether the index exists, but when the index does not exist it then callsSearchIndex.createIfNoneExists(...), which performs another index LIST internally. This adds an extra network round-trip on the create path (latency + potential throttling). Consider refactoring to reuse the already-fetched index list (e.g., by plumbing a cached result intoSearchIndex.createIfNoneExists/ itsSearchIndexClient).
val existingIndexJsonOpt = if (getExisting(auth, serviceName, apiVersion).contains(indexName)) {
if (indexJsonOpt.isDefined) {
println(f"indexJsonOpt is specified, however an index for $indexName already exists," +
f"we will use the index definition obtained from the existing index instead")
}
- Files reviewed: 15/15 changed files
- Comments generated: 0 new
- Review effort level: Lite
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
Related work
Problem
SynapseML defaulted to the deprecated
2023-07-01-Previewdata-plane API. Moving to the stable2026-04-01API also requires adopting the profile-based vector schema without breaking the published Scala model or dropping valid service fields.Changes
2026-04-01.2023-10-01-Previewand later as profile-based vector APIs:vectorSearch.algorithmsandvectorSearch.profilesvectorSearchProfilesearchable: trueon vector fieldsVectorSearchandIndexFieldcase-class constructor, companion, and extractor shapes unchanged.apiVersion=2023-07-01-Preview.createIfNoneExistsvalidates the actual remote schema with one LIST plus one GET and fails early with migration guidance instead of implying that a no-op create migrated the index.vectorQueries: [{"kind":"vector","vector":[...],"fields":"embeddings","k":5}].Release compatibility
The Spark 4.1 replay needs the AAD authentication precursor from #2591, which is already on
masterbut not yet on the release branch. The compatibility job now reads an ordered manifest of full target-branch commit SHAs, applies only their release-relevant paths, skips content already present, and then replays this PR. This keeps the check generic and removes the prerequisite automatically once the release branch contains the backport.Existing-index behavior
This PR does not destructively rewrite existing indexes. If a legacy vector index is opened with a modern API version, SynapseML explains the two safe options:
apiVersion=2023-07-01-Preview.Modern API responses that normalize a legacy vector field to dimensions plus a null profile are still detected as legacy. The shared document-QA sample pins writes to 2023-07-01-Preview until its persistent index is explicitly migrated; its vector query remains on 2026-04-01.
Validation
cognitive/Test/compileVectorSchemaMigrationSuite: 15/15 secret-free teststools/ci/tests/test_pipeline_yaml.py: 16/16 tests, including an isolated prerequisite-before-PR Git replayspark4.1cognitive/scalastyleandcognitive/Test/scalastyleDependencies
No dependency changes.