Skip to content

Index images without OCR text and route vision analysis through its AI connection - #1584

Merged
Paul Lizer (paullizer) merged 4 commits into
microsoft:Developmentfrom
paullizer:paullizer-fix-text-free-image-indexing
Sep 30, 2026
Merged

Paul Lizer (paullizer) merged 4 commits into
microsoft:Developmentfrom
paullizer:paullizer-fix-text-free-image-indexing

Conversation

@paullizer

Copy link
Copy Markdown
Contributor

Refs #1583

Summary

Text-free images, such as an overexposed photo of an empty lab floor, ended with Processing complete - no text found in image: no title, 0 chunks in AI Search, and nothing chat could use, even though the vision model had described them. This PR indexes the vision description when OCR finds no text, falls back to Content Understanding's image analyzer for standalone images, and calls the vision model on the AI connection that actually hosts it.

It also raises the default Azure OpenAI image generation API version to 2025-04-01-preview (separate section below). One version bump, 0.261.047, covers both.

Root cause

  • Only OCR text could create an image chunk. CU prebuilt-documentSearch returned no pages, so extraction fell back to DI prebuilt-layout. Layout read floor tape as the page number J (<!-- PageNumber="J" -->) on one photo and returned nothing for the other. The bogus comment became the first photo's only chunk; the second got none, so final metadata extraction was skipped too.
  • The vector ignored the description. save_chunks appended the vision block after embedding the OCR text (embedding - 7 tokens).
  • CU's image description was discarded. prebuilt-imageSearch returns ![image](pages/1) as markdown and the description in fields.Summary.valueString, which analyze_image_with_content_understanding ignored.
  • The vision model ignored its AI connection. analyze_image_with_vision_model always used the legacy azure_openai_gpt_*/APIM client, so gpt-5.6-luna (deployed only on another connection) returned 404, while the admin test resolved the real connection and passed.

Changes

Index image descriptions when OCR finds no text (functions_documents.py)

  • _format_vision_analysis_block() replaces the duplicated inline formatting. Valid analyses keep the exact === AI Vision Analysis === format; missing, non-dict, error, or empty analyses return ''.
  • save_chunks() builds the stored text (OCR + vision block, no leading blank lines when OCR is empty) before embedding and embeds that same text, keeping the last-resort clamp guard. It never writes an empty chunk. save_chunks_batch() uses the same helpers.
  • process_di_document() records whether this run produced a usable vision analysis, keeps only image OCR pages with real text, and otherwise saves one description-only page-1 chunk (flagged vision_description_only). Final metadata extraction then runs. Images with real OCR text are unchanged; vision still runs only with Enhanced Citations on.

Content Understanding image analyzer fallback (functions_content_understanding.py, functions_content.py, route_frontend_chats.py)

  • analyze_image_with_content_understanding() emits Summary as plain text and other string fields as Name: value, ignores placeholder-only markdown, and keeps figure summaries. This also fixes images embedded in DOCX/PPTX files analyzed with CU, which were indexed with just the ![image](pages/1) placeholder.
  • extract_content_with_extraction_engine(..., is_image=False): when the document analyzer returns nothing for an image, the image analyzer's description becomes page 1 with the reason Content Understanding found no text, so its image analyzer description was indexed. If it fails or returns nothing, extraction falls back to DI Layout exactly as before (with a warning). PDFs and other files are unchanged. Workspace uploads thread is_image from process_di_document; direct chat uploads pass is_image=is_image_file.

Route the vision model through its AI connection (functions_model_endpoint_types.py, functions_documents.py, route_backend_settings.py)

  • New pure find_enabled_model_endpoint_for_model_name(endpoints, model_name) → (endpoint, model) or (None, None): enabled endpoints/models only; deployment name or resolved request model first, then model name, then id; first match in endpoint order.
  • _build_model_endpoint_client_for_model() now holds the client construction moved out of _resolve_metadata_extraction_client (Key Vault resolution, request model, API version/protocol checks, provider allowlist). Metadata extraction keeps the same behavior, order, and error messages.
  • analyze_image_with_vision_model() uses the matched connection and sends model=<resolved request model> via resolve_vision_model_endpoint(); otherwise the legacy APIM/direct client is unchanged. It logs the connection by name/id only and still returns None on errors. Chat image uploads benefit automatically.
  • _test_multimodal_vision_connection() now resolves the name the same way, so Test Vision Analysis uses the ingestion connection, including when APIM is checked with multi-endpoint models enabled (previously it tested APIM while ingestion would not).

Image generation API version default

  • azure_openai_image_gen_api_version now defaults to AZURE_OPENAI_IMAGE_GEN_API_VERSION_DEFAULT = '2025-04-01-preview', the newest dated Azure OpenAI version and the one documented for gpt-image-1, 1.5, and 2. 2024-12-01-preview predates gpt-image support.
  • normalize_image_generation_api_version_settings(), run from normalize_loaded_settings(), upgrades a stored value that is exactly the previous default (AZURE_OPENAI_IMAGE_GEN_API_VERSION_PREVIOUS_DEFAULT) or blank. Any other value is kept. azure_apim_image_gen_api_version is never changed because the gateway defines its version.
  • Updated docs/admin/ai-models.md, artifacts/cosmos_examples/cosmos-settings-example.json, and the database seeder admin_settings.json (image generation entries only).

Tests

  • functional_tests/test_image_description_indexing_without_ocr_text.py (8 tests): pure lookup tests in-process; real modules in fresh normal and -O processes with external I/O blocked, reusing the issue payloads (CU prebuilt-imageSearch result, empty/PageNumber="J" DI output). Covers vision block formatting, save/embed parity, description-only chunks, header-only OCR kept, CU Summary parsing and fallback, vision connection lookup and legacy fallback, unchanged metadata-extraction client behavior and messages, the admin test agreement, and the chat call site.
  • functional_tests/test_image_generation_api_version_default.py (17 tests): default, migration cases (old default, blank, custom 2025-03-01-preview untouched, APIM untouched), wiring, docs/artifacts, and a real get_settings() load that persists the migration.
  • Re-ran related suites (Content Understanding, Office embedded images, figure chunks, vision multi-endpoint, DI extraction mode and reprocess, privacy logging audit, model endpoint providers, settings store/migration suites, docs coverage and quality, route policy suites), plus the BAC, XSS, and swagger guardrails: all pass. Pre-existing failures are unchanged: test_markdown_chunk_size_enforcement.py (4/8), test_vision_model_parameter_fix.py, test_document_auto_metadata_extraction_consistency.py (3/4), test_multiselect_metadata_extraction.py (4/5), test_image_generation_model_compatibility.py (2/4).

Notes

  • Existing images are not re-indexed automatically; re-upload affected images (Change Extraction re-indexes but does not re-run metadata extraction).
  • Only PageNumber/PageBreak comments count as non-text; PageHeader/PageFooter comments carry recognized text and still count, so header- or footer-only images are not dropped.
  • The vision setting stores only a name, so a name present on several enabled connections resolves by the precedence above.

Docs: docs/explanation/fixes/IMAGE_DESCRIPTION_INDEXING_WITHOUT_OCR_TEXT_FIX.md, docs/explanation/fixes/IMAGE_GENERATION_API_VERSION_DEFAULT_FIX.md, release notes under 0.261.047, and updates to docs/admin/knowledge.md, docs/admin/workspaces.md, and the Content Understanding feature doc.

…I connection

Text-free images such as an overexposed photo of an empty lab floor ended with no
search chunk even though the vision model described them (microsoft#1583).

- Index an image's AI vision description on its own when OCR finds no text, and
  treat Layout output that is only page-number or page-break annotations (for
  example <!-- PageNumber="J" -->) as no text. Header and footer text still counts.
- Build the stored chunk text, including the vision block, before embedding so
  save_chunks and save_chunks_batch embed exactly what they store. Never index a
  failed or empty vision analysis, and never write an empty chunk.
- Read string fields, including Summary, from the Content Understanding image
  analyzer, and fall back to it for standalone images (workspace and chat uploads)
  when the document analyzer returns nothing, before Document Intelligence Layout.
- Resolve the stored vision model name to the enabled AI connection that hosts it
  with the pure find_enabled_model_endpoint_for_model_name(), share the client
  construction with metadata extraction, and make Test Vision Analysis use the
  same connection as ingestion.

Refs microsoft#1583

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2024-12-01-preview predates gpt-image support. 2025-04-01-preview is the newest
dated Azure OpenAI API version and is documented for gpt-image-1, 1.5, and 2.

- Add AZURE_OPENAI_IMAGE_GEN_API_VERSION_DEFAULT and use it as the
  azure_openai_image_gen_api_version default.
- Add normalize_image_generation_api_version_settings(), run on every settings
  load, to upgrade a stored value that is exactly the previous default
  (AZURE_OPENAI_IMAGE_GEN_API_VERSION_PREVIOUS_DEFAULT) or blank. Any other value
  is kept, and azure_apim_image_gen_api_version is never changed.
- Update the admin docs default and the example and seeder settings artifacts.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Covers the text-free image indexing and vision routing fix (microsoft#1583) and the image
generation API version default.

Refs microsoft#1583

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Comment thread application/single_app/functions_content.py
- Assert the token usage of the no-vision save_chunks_batch case, so the
  value is read before it is reassigned (py/multiple-definition).
- Add a fresh-process probe that imports functions_content_understanding
  before functions_content and checks the image analyzer fallback, so both
  import orders are covered for the pre-existing functions_content ->
  functions_content_understanding -> functions_settings ->
  functions_document_actions -> functions_search -> functions_content cycle
  (py/cyclic-import). This PR adds no new module dependency; document the
  cycle and defer breaking it.

Refs microsoft#1583

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@paullizer
Paul Lizer (paullizer) merged commit be01d94 into microsoft:Development Sep 30, 2026
12 checks passed
Paul Lizer (paullizer) added a commit that referenced this pull request Sep 30, 2026
…I connection

Folded from the Development fix (#1584) for #1583:
- An image whose OCR output is empty, or only a Document Intelligence page-number comment, is
  indexed from its AI vision description, and chunk embeddings are built from the stored text
  including the vision block.
- Standalone images fall back to the Content Understanding image analyzer, whose Summary field
  is now read, before Document Intelligence Layout.
- The vision model name resolves to the enabled AI connection that hosts it, for ingestion,
  chat image uploads and the admin vision test.

React V2 adjustments:
- The shared model-endpoint client helper keeps V2's order: find the model and re-check its
  chat capability before resolving Key Vault secrets. Metadata extraction behaves as before.
- Vision analysis re-checks image support on the resolved connection model, and the legacy
  path keeps its existing checks.
- A page that fits the embedding budget embeds its OCR text when the vision block would push
  it over, instead of being refused by the non-legacy embedding guard.
- The V2 admin help text and the Knowledge docs describe the new behavior.

(cherry picked from commit b91f9d5)

Refs #1583

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Paul Lizer (paullizer) added a commit that referenced this pull request Sep 30, 2026
Folded from the Development fix (#1584): the image generation API version defaults to
2025-04-01-preview, the newest dated Azure OpenAI version and the one documented for gpt-image
models, and a saved 2024-12-01-preview (the previous default) or blank value is upgraded when
settings load. APIM versions are never changed.

React V2 adjustments:
- Image connection profiles imported with the previous default are upgraded the same way, and
  APIM profiles are left alone.
- Unmigrated legacy Images settings with no version fall back to 2025-04-01-preview.
- The V2 admin field default and the AI Models and image generation docs match.

(cherry picked from commit 4882390)

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants