diff --git a/application/single_app/admin_settings_fields.py b/application/single_app/admin_settings_fields.py
index d3880036a..96bf8ce13 100644
--- a/application/single_app/admin_settings_fields.py
+++ b/application/single_app/admin_settings_fields.py
@@ -30,7 +30,7 @@
to the same normalizers the server-rendered form uses. Both interfaces
therefore agree on what a valid value is.
-Beyond a field's type, four optional descriptors shape how a section reads:
+Beyond a field's type, five optional descriptors shape how a section reads:
``group``
Names the cluster a field belongs to, either as a label or with a variant
@@ -40,6 +40,12 @@
Intelligence is a flat run of forty controls in which the credential that
makes the rest work is simply the last one.
+ A group may also name ``open_until_set``: a key in the same group whose
+ blank value opens the group while the section's capability is on. That is
+ for an optional connection the administrator most likely wants next --
+ Content Understanding under Enhanced extraction -- which cannot be marked
+ ``required`` because the capability works without it.
+
``depends_on``
Shows a field only while a condition holds. See ``evaluate_dependency`` for
the supported shapes; ``any_of`` exists because the Speech resource block is
@@ -59,6 +65,17 @@
input would otherwise save to a top-level key nothing reads. See
``_apply_nested_paths``.
+``on_enable``
+ Companion values a switch sets when it is turned on, as
+ ``{"set": {key: value}, "when": }``. Enhanced extraction is the
+ case: the extraction mode only matters while it is on, and turning it on
+ with the mode still on Standard would change nothing, so the mode moves to
+ Auto, which is what the server-rendered form has always done. The browser
+ applies it to the draft as the switch flips, so the new value is visible
+ before saving; ``_apply_enable_defaults`` applies it to a save that turns
+ the switch on without naming the companion, so an API client gets the same
+ result. A value named in the same save always wins.
+
The Appearance, Agents & Actions, Chat, Knowledge, Workflow, Workspaces and
Security groups are described in full. Sections with no entry here fall back to the V2 surface's
``enable_*`` scan, so undescribed groups keep working exactly as they did. A
@@ -1844,9 +1861,11 @@
# and then scrolls past everything that depends on the connection before
# reaching the connection itself.
#
- # Here the connection comes first and the behaviour that needs it follows,
- # declaring `requires` so a toggle flipped without a configured endpoint says
- # so rather than silently doing nothing.
+ # Here Document Intelligence is the connection and nothing else. It is needed
+ # whatever else is configured -- Standard extraction is its Read model, and
+ # Enhanced falls back to its Layout model -- so it comes first, and the
+ # settings that only matter once Enhanced extraction is on live in the
+ # section after it.
# ------------------------------------------------------------------
"document-intelligence-section": [
{
@@ -1864,8 +1883,9 @@
"label": "Connection",
"variant": "connection",
"help": (
- "Document Intelligence reads PDFs and images. Nothing else in this "
- "tab works until it is reachable."
+ "Document Intelligence reads PDFs and images. Standard extraction "
+ "is its Read model and Enhanced extraction falls back to its Layout "
+ "model, so nothing else in this tab works until it is reachable."
),
},
},
@@ -1935,11 +1955,15 @@
"test_type": "azure_doc_intelligence",
"test_payload": {
"enable_apim": {"key": "enable_document_intelligence_apim"},
+ # With Enhanced extraction off every document uses Standard,
+ # whatever mode is stored, so the test exercises Read too.
"document_intelligence_pdf_image_extraction_mode": {
- "key": "document_intelligence_pdf_image_extraction_mode"
+ "key": "document_intelligence_pdf_image_extraction_mode",
+ "when": {"key": "enable_enhanced_extraction", "equals": True},
},
"document_intelligence_auto_sample_pages": {
- "key": "document_intelligence_auto_sample_pages"
+ "key": "document_intelligence_auto_sample_pages",
+ "when": {"key": "enable_enhanced_extraction", "equals": True},
},
"direct.endpoint": {
"key": "azure_document_intelligence_endpoint",
@@ -1964,15 +1988,61 @@
},
"group": {"id": "connection", "label": "Connection", "variant": "connection"},
},
+ ],
+ # ------------------------------------------------------------------
+ # Knowledge / Document Extraction / Enhanced Extraction
+ #
+ # One card for the capability and everything it governs. Enhanced extraction
+ # used to be a switch inside the collapsed Extraction group of Document
+ # Intelligence, while Content Understanding -- the engine behind it -- was a
+ # card of its own that could be filled in with Enhanced off, where it is
+ # never called. The switch now leads this card, and every setting that only
+ # means something while it is on sits beneath it and is hidden while it is
+ # off, so the relationship is shown rather than remembered.
+ #
+ # Content Understanding stays optional: without it Enhanced uses Document
+ # Intelligence Layout, which still captures tables and checkbox states but
+ # does not describe figures. The engine notice says which applies. Its
+ # fields are also gated on the cloud, because the service is not offered in
+ # Azure Government or custom clouds; the server-rendered pane does the same
+ # with ``content_understanding_supported``.
+ # ------------------------------------------------------------------
+ "enhanced-extraction-section": [
+ {
+ "key": "enable_enhanced_extraction",
+ "type": "switch",
+ "label": "Enable Enhanced extraction",
+ "help": (
+ "Extracts tables, page structure, forms and checkbox states from PDFs "
+ "and images, and with Azure AI Content Understanding connected, "
+ "descriptions of figures and charts. The extraction mode and the "
+ "Content Understanding connection appear here once it is on. While it "
+ "is off, every document uses Standard extraction (Document "
+ "Intelligence Read)."
+ ),
+ "default": False,
+ "role": "capability",
+ "on_enable": {
+ # Enhanced with the mode still on Standard would change nothing.
+ # The server-rendered form moves to Auto at this point as well.
+ "set": {"document_intelligence_pdf_image_extraction_mode": "auto"},
+ "when": {
+ "key": "document_intelligence_pdf_image_extraction_mode",
+ "equals": "read",
+ },
+ },
+ },
{
"key": "document_intelligence_pdf_image_extraction_mode",
"type": "select",
"label": "PDF and Image Extraction Mode",
"help": (
- "Standard is Document Intelligence Read: fastest and cheapest for plain "
- "text. Enhanced captures tables, page structure, forms and checkbox "
- "states, at roughly six times the cost per thousand pages. Auto samples "
- "the first pages and picks."
+ "What new PDF and image uploads use. Standard is Document Intelligence "
+ "Read: fastest and cheapest for plain text. Enhanced captures tables, "
+ "page structure, forms and checkbox states, at roughly six times the "
+ "cost per thousand pages. Auto samples the first pages and picks. With "
+ "Standard selected, a document can still be extracted again as "
+ "Enhanced from its workspace."
),
"default": "read",
"options": [
@@ -1986,7 +2056,7 @@
"label": "Auto — sample first pages, then choose",
},
],
- "group": {"id": "extraction", "label": "Extraction", "variant": "behavior"},
+ "depends_on": {"key": "enable_enhanced_extraction", "equals": True},
},
{
"key": "document_intelligence_auto_sample_pages",
@@ -2000,53 +2070,63 @@
"default": 3,
"min": 1,
"max": 20,
- "group": {"id": "extraction", "label": "Extraction", "variant": "behavior"},
- "depends_on": {
- "key": "document_intelligence_pdf_image_extraction_mode",
- "equals": "auto",
- },
+ "depends_on": [
+ {"key": "enable_enhanced_extraction", "equals": True},
+ {"key": "document_intelligence_pdf_image_extraction_mode", "equals": "auto"},
+ ],
},
{
- "key": "enable_enhanced_extraction",
+ "key": "enable_document_intelligence_formula_extraction",
"type": "switch",
- "label": "Enable Enhanced extraction",
+ "label": "Extract mathematical formulas",
"help": (
- "Uses Azure AI Content Understanding, which returns tables, page "
- "structure, checkbox states and descriptions of figures and charts. "
- "Falls back to Document Intelligence Layout where Content Understanding "
- "is unavailable or unconfigured."
+ "Captures equations as LaTeX rather than approximate OCR text. It is a "
+ "billed add-on to the Document Intelligence Layout model, so it adds "
+ "per-page cost wherever Layout runs: Auto's page sampling, and Enhanced "
+ "extraction without Content Understanding. Standard and Content "
+ "Understanding extractions do not use it."
),
"default": False,
- "group": {"id": "extraction", "label": "Extraction", "variant": "behavior"},
+ "depends_on": {"key": "enable_enhanced_extraction", "equals": True},
},
{
- "key": "enable_document_intelligence_formula_extraction",
- "type": "switch",
- "label": "Extract mathematical formulas",
+ "type": "component",
+ "component": "enhanced-extraction-engine",
+ "label": "Extraction engine",
"help": (
- "Captures equations as LaTeX rather than approximate OCR text. This is a "
- "billed Document Intelligence add-on that adds per-page cost to every "
- "Enhanced extraction, and it has no effect while extraction is Standard."
+ "Which engine runs when a document is extracted as Enhanced, judged "
+ "from the settings on this page. Test the Content Understanding "
+ "connection to confirm it is reachable."
),
- "default": False,
- "group": {"id": "extraction", "label": "Extraction", "variant": "behavior"},
+ "depends_on": {"key": "enable_enhanced_extraction", "equals": True},
},
- ],
- # Its own section now. The card has always been in the extraction pane but
- # was missing from ADMIN_NAV, so neither interface could navigate to it.
- "content-understanding-section": [
{
"key": "azure_content_understanding_endpoint",
"type": "text",
"label": "Foundry Endpoint",
"help": (
"The Microsoft Foundry resource endpoint, with no trailing path. Leave "
- "blank and Enhanced extraction falls back to Document Intelligence "
- "Layout."
+ "it blank to use Document Intelligence Layout instead."
),
"default": "",
"placeholder": "https://your-resource.services.ai.azure.com",
- "group": {"id": "connection", "label": "Connection", "variant": "connection"},
+ "group": {
+ "id": "content-understanding",
+ "label": "Content Understanding connection",
+ "variant": "connection",
+ "help": (
+ "Optional. Azure AI Content Understanding backs Enhanced extraction "
+ "and adds descriptions of figures and charts. Without it, Enhanced "
+ "uses Document Intelligence Layout."
+ ),
+ # Starts open while unconnected, because it is the step most
+ # administrators want next after turning Enhanced on.
+ "open_until_set": "azure_content_understanding_endpoint",
+ },
+ "depends_on": [
+ {"key": "enable_enhanced_extraction", "equals": True},
+ {"flag": "content_understanding_supported", "equals": True},
+ ],
},
{
"key": "azure_content_understanding_authentication_type",
@@ -2061,18 +2141,58 @@
{"value": "key", "label": "Key"},
{"value": "managed_identity", "label": "Managed Identity"},
],
- "group": {"id": "connection", "label": "Connection", "variant": "connection"},
+ "group": {
+ "id": "content-understanding",
+ "label": "Content Understanding connection",
+ "variant": "connection",
+ },
+ "depends_on": [
+ {"key": "enable_enhanced_extraction", "equals": True},
+ {"flag": "content_understanding_supported", "equals": True},
+ ],
},
{
"key": "azure_content_understanding_key",
"type": "secret",
"default": "",
"label": "Content Understanding Key",
- "group": {"id": "connection", "label": "Connection", "variant": "connection"},
- "depends_on": {
- "key": "azure_content_understanding_authentication_type",
- "equals": "key",
+ "group": {
+ "id": "content-understanding",
+ "label": "Content Understanding connection",
+ "variant": "connection",
+ },
+ "depends_on": [
+ {"key": "enable_enhanced_extraction", "equals": True},
+ {"flag": "content_understanding_supported", "equals": True},
+ {"key": "azure_content_understanding_authentication_type", "equals": "key"},
+ ],
+ },
+ {
+ "type": "component",
+ "component": "connection-test",
+ "label": "Test Content Understanding connection",
+ "test_type": "content_understanding",
+ "test_payload": {
+ "endpoint": {"key": "azure_content_understanding_endpoint"},
+ "authentication_type": {
+ "key": "azure_content_understanding_authentication_type"
+ },
+ "key": {"key": "azure_content_understanding_key"},
+ "api_version": {"key": "azure_content_understanding_api_version"},
+ "analyzer_id": {"key": "azure_content_understanding_analyzer_id"},
+ "image_analyzer_id": {
+ "key": "azure_content_understanding_image_analyzer_id"
+ },
},
+ "group": {
+ "id": "content-understanding",
+ "label": "Content Understanding connection",
+ "variant": "connection",
+ },
+ "depends_on": [
+ {"key": "enable_enhanced_extraction", "equals": True},
+ {"flag": "content_understanding_supported", "equals": True},
+ ],
},
{
"key": "azure_content_understanding_api_version",
@@ -2080,7 +2200,15 @@
"label": "API Version",
"default": "",
"fallback_when_empty": True,
- "group": {"id": "analyzers", "label": "Analyzers", "variant": "advanced"},
+ "group": {
+ "id": "analyzers",
+ "label": "Content Understanding analyzers",
+ "variant": "advanced",
+ },
+ "depends_on": [
+ {"key": "enable_enhanced_extraction", "equals": True},
+ {"flag": "content_understanding_supported", "equals": True},
+ ],
},
{
"key": "azure_content_understanding_analyzer_id",
@@ -2088,7 +2216,15 @@
"label": "Document Analyzer",
"default": "",
"fallback_when_empty": True,
- "group": {"id": "analyzers", "label": "Analyzers", "variant": "advanced"},
+ "group": {
+ "id": "analyzers",
+ "label": "Content Understanding analyzers",
+ "variant": "advanced",
+ },
+ "depends_on": [
+ {"key": "enable_enhanced_extraction", "equals": True},
+ {"flag": "content_understanding_supported", "equals": True},
+ ],
},
{
"key": "azure_content_understanding_image_analyzer_id",
@@ -2096,29 +2232,18 @@
"label": "Image Analyzer",
"default": "",
"fallback_when_empty": True,
- "group": {"id": "analyzers", "label": "Analyzers", "variant": "advanced"},
- },
- {
- "type": "component",
- "component": "connection-test",
- "label": "Test Content Understanding connection",
- "test_type": "content_understanding",
- "test_payload": {
- "endpoint": {"key": "azure_content_understanding_endpoint"},
- "authentication_type": {
- "key": "azure_content_understanding_authentication_type"
- },
- "key": {"key": "azure_content_understanding_key"},
- "api_version": {"key": "azure_content_understanding_api_version"},
- "analyzer_id": {"key": "azure_content_understanding_analyzer_id"},
- "image_analyzer_id": {
- "key": "azure_content_understanding_image_analyzer_id"
- },
+ "group": {
+ "id": "analyzers",
+ "label": "Content Understanding analyzers",
+ "variant": "advanced",
},
- "group": {"id": "connection", "label": "Connection", "variant": "connection"},
+ "depends_on": [
+ {"key": "enable_enhanced_extraction", "equals": True},
+ {"flag": "content_understanding_supported", "equals": True},
+ ],
},
],
- # Also promoted out of the extraction card into a section of its own. It is
+ # Promoted out of the extraction card into a section of its own. It is
# independent of Enhanced extraction despite sitting inside it in the V1
# markup, which is what made it read as part of that feature.
"office-embedded-image-section": [
@@ -6587,23 +6712,30 @@ def get_nested_path_fields():
}
-def evaluate_dependency(dependency, read_value):
+def evaluate_dependency(dependency, read_value, runtime_flags=None):
"""Whether a ``depends_on`` condition holds, given a value reader.
``read_value`` takes a settings key and returns its current value, so the
same rules apply whether the caller is reading a stored document or a draft
that has not been saved yet.
- Four shapes are supported, and they compose:
+ Five shapes are supported, and they compose:
``{"key": k, "equals": v}`` k currently equals v
``{"key": k, "not_equals": v}`` k currently differs from v
+ ``{"flag": f, "equals": b}`` runtime flag f is b
``{"any_of": [...]}`` at least one nested condition holds
``{"all_of": [...]}`` every nested condition holds
``equals`` against a boolean compares truthiness rather than identity,
because a settings document written by the server-rendered form stores
checkbox state as the string ``"on"``.
+
+ A runtime flag is resolved by the server rather than read from the settings
+ document -- whether Content Understanding is offered in this cloud, say.
+ ``runtime_flags`` supplies them. Without it a flag condition counts as met,
+ as ``_dependency_is_satisfied`` treats it, so validation is never suppressed
+ for a field behind a gate the caller cannot see.
"""
if not dependency:
return True
@@ -6611,18 +6743,28 @@ def evaluate_dependency(dependency, read_value):
# A list means every condition has to hold. It is the shorthand the Security
# and Workspaces sections declare, and is equivalent to ``all_of``.
if isinstance(dependency, list):
- return all(evaluate_dependency(nested, read_value) for nested in dependency)
+ return all(
+ evaluate_dependency(nested, read_value, runtime_flags) for nested in dependency
+ )
if "any_of" in dependency:
return any(
- evaluate_dependency(nested, read_value) for nested in dependency["any_of"]
+ evaluate_dependency(nested, read_value, runtime_flags)
+ for nested in dependency["any_of"]
)
if "all_of" in dependency:
return all(
- evaluate_dependency(nested, read_value) for nested in dependency["all_of"]
+ evaluate_dependency(nested, read_value, runtime_flags)
+ for nested in dependency["all_of"]
)
+ if dependency.get("flag"):
+ if runtime_flags is None:
+ return True
+ expected = _coerce_bool(dependency.get("equals", True))
+ return bool(runtime_flags.get(dependency["flag"])) is expected
+
current = read_value(dependency["key"])
if "not_equals" in dependency:
@@ -7573,6 +7715,10 @@ def normalize_admin_settings_updates(updates, current_settings=None):
_check_acknowledgements(updates, current, errors)
+ # A switch turned on may bring companion values with it. Applied before the
+ # checks below so they judge the state that will actually be stored.
+ _apply_enable_defaults(normalized, current, warnings)
+
# "At least one" style constraints can only be judged once the whole payload
# is known, because the capability toggle and its selection may arrive apart.
_check_minimum_selections(normalized, current, errors)
@@ -7670,6 +7816,58 @@ def _check_planner_model_selection(normalized, current_settings, errors):
errors[key] = message
+def _apply_enable_defaults(normalized, current_settings, warnings):
+ """Set the companion values a switch declares through ``on_enable``.
+
+ The browser does this to its draft as the switch flips. Doing it here as
+ well means a save that turns Enhanced extraction on through the API without
+ naming a mode still moves Standard to Auto, rather than enabling a
+ capability that then changes nothing.
+
+ Only a transition counts: re-saving a switch that is already on leaves its
+ companions alone, and a companion named in the same save is the
+ administrator's explicit choice and always wins.
+ """
+ def merged(key):
+ if key in normalized:
+ return normalized[key]
+ if key in current_settings:
+ return current_settings[key]
+ # A key that has never been saved reads as its declared default, which
+ # is what get_settings would have merged in.
+ definition = get_field_definition(key) or {}
+ return definition.get("default")
+
+ for _section_id, field in iter_fields():
+ key = field.get("key")
+ effect = field.get("on_enable")
+ if not key or not effect or field.get("readonly"):
+ continue
+ if key not in normalized or not _coerce_bool(normalized[key]):
+ continue
+ if _coerce_bool(current_settings.get(key)):
+ continue
+ if not evaluate_dependency(effect.get("when"), merged):
+ continue
+
+ for companion, value in (effect.get("set") or {}).items():
+ if companion in normalized:
+ continue
+ normalized[companion] = value
+ companion_field = get_field_definition(companion) or {}
+ shown = next(
+ (
+ option.get("label", value)
+ for option in companion_field.get("options", [])
+ if option.get("value") == value
+ ),
+ value,
+ )
+ warnings[companion] = (
+ f'Set to "{shown}" because {field.get("label", key)} was turned on.'
+ )
+
+
def _apply_cross_field_rules(normalized, current_settings, warnings):
"""Reconcile settings whose valid range depends on another setting.
diff --git a/application/single_app/admin_settings_nav.py b/application/single_app/admin_settings_nav.py
index f63b48806..110461cb3 100644
--- a/application/single_app/admin_settings_nav.py
+++ b/application/single_app/admin_settings_nav.py
@@ -278,11 +278,15 @@
"icon": "bi-file-earmark-text",
"sections": [
{"id": "document-intelligence-section", "label": "Document Intelligence", "icon": "bi-file-earmark-text"},
- # These two cards have always existed in the extraction pane but
- # were absent from this list, so neither interface could
- # navigate to them. Their ids are the ones already in the
- # markup, so the server-rendered sidebar resolves them as-is.
- {"id": "content-understanding-section", "label": "Content Understanding", "icon": "bi-stars"},
+ # Enhanced extraction and the Content Understanding connection
+ # that backs it are one section, led by the switch. Content
+ # Understanding used to be a section of its own, which let it be
+ # configured while Enhanced was off and it could never run.
+ {"id": "enhanced-extraction-section", "label": "Enhanced Extraction", "icon": "bi-stars"},
+ # This card has always existed in the extraction pane but was
+ # absent from this list, so neither interface could navigate to
+ # it. Its id is the one already in the markup, so the
+ # server-rendered sidebar resolves it as-is.
{"id": "office-embedded-image-section", "label": "Images Inside Office Files", "icon": "bi-images"},
{"id": "chunk-size-section", "label": "Chunk Sizes", "icon": "bi-collection"},
# The upload ceiling belongs with the extraction pipeline it
diff --git a/application/single_app/config.py b/application/single_app/config.py
index bb746dac7..6bd64619f 100644
--- a/application/single_app/config.py
+++ b/application/single_app/config.py
@@ -101,7 +101,7 @@
EXECUTOR_TYPE = 'thread'
EXECUTOR_MAX_WORKERS = 30
SESSION_TYPE = 'filesystem'
-VERSION = "0.261.264"
+VERSION = "0.261.265"
IS_DEVELOPMENT = is_development_env_enabled()
# Opt-out for deployments where App Service Easy Auth is active but the platform
diff --git a/application/single_app/route_backend_v2.py b/application/single_app/route_backend_v2.py
index 424079ae1..cb4e9f7ab 100644
--- a/application/single_app/route_backend_v2.py
+++ b/application/single_app/route_backend_v2.py
@@ -140,6 +140,7 @@
is_admin_settings_redacted_secret,
is_chat_file_upload_enabled_for_user,
is_chat_workflow_results_enabled_for_user,
+ is_content_understanding_supported_environment,
is_user_workflows_enabled_for_user,
is_workflow_assistant_enabled_for_user,
merge_model_endpoint_payload,
@@ -1409,7 +1410,14 @@ def v2_admin_get_settings():
# rather than a stored setting. Inbound MCP is gated by an
# App Service application setting, so its value cannot be
# read out of the settings document the SPA already holds.
- "runtime_flags": {"mcp_ui_enabled": is_mcp_ui_enabled()},
+ # Content Understanding is not offered in every Azure cloud,
+ # which only the server's AZURE_ENVIRONMENT can say.
+ "runtime_flags": {
+ "mcp_ui_enabled": is_mcp_ui_enabled(),
+ "content_understanding_supported": (
+ is_content_understanding_supported_environment()
+ ),
+ },
"suppressed_capabilities": get_suppressed_capability_keys(),
"version": VERSION,
}
diff --git a/application/single_app/templates/admin/_panes/extraction.html b/application/single_app/templates/admin/_panes/extraction.html
index 5030744d6..857acb69f 100644
--- a/application/single_app/templates/admin/_panes/extraction.html
+++ b/application/single_app/templates/admin/_panes/extraction.html
@@ -11,7 +11,8 @@ Document Intelligence
Configure Azure Document Intelligence settings. Document Intelligence always powers
Standard extraction and is required for workspaces and chat file uploads.
-