From d46eef2cd3b1e2ed71836bfd5fce5cf2fa8bd405 Mon Sep 17 00:00:00 2001 From: chienyuanchang Date: Wed, 3 Jun 2026 10:14:26 -0700 Subject: [PATCH 1/7] first version --- .../CHANGELOG.md | 4 + .../azure/ai/contentunderstanding/_helpers.py | 66 ++++++++++-- .../tests/samples/test_sample_to_llm_input.py | 16 +-- .../samples/test_sample_to_llm_input_async.py | 16 +-- .../tests/test_to_llm_input.py | 102 +++++++++++++++--- 5 files changed, 171 insertions(+), 33 deletions(-) diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/CHANGELOG.md b/sdk/contentunderstanding/azure-ai-contentunderstanding/CHANGELOG.md index 5d0eb9112b59..0b3ebbab185f 100644 --- a/sdk/contentunderstanding/azure-ai-contentunderstanding/CHANGELOG.md +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/CHANGELOG.md @@ -5,6 +5,10 @@ ### Features Added - Added `to_llm_input` helper function that converts `AnalysisResult` objects into LLM-friendly text with YAML front matter and markdown content. Supports documents, audio/video, and classification hierarchies. +### Bugs Fixed +- Updated `to_llm_input` page markers from `` to `` and avoided duplicate marker injection when the service markdown already includes `InputPageNumber` markers. +- Filtered service-emitted `LLMStats:` telemetry entries from the rendered `rai_warnings` front matter. + ## 1.1.0 (2026-04-20) ### Features Added diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/azure/ai/contentunderstanding/_helpers.py b/sdk/contentunderstanding/azure-ai-contentunderstanding/azure/ai/contentunderstanding/_helpers.py index 19ff1638b9e0..7156c1effae4 100644 --- a/sdk/contentunderstanding/azure-ai-contentunderstanding/azure/ai/contentunderstanding/_helpers.py +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/azure/ai/contentunderstanding/_helpers.py @@ -15,7 +15,7 @@ import datetime import math import re -from typing import Any, Dict, List, Optional, TYPE_CHECKING +from typing import Any, Dict, List, Optional, Tuple, TYPE_CHECKING if TYPE_CHECKING: from .models import ( @@ -39,6 +39,37 @@ } ) +# Marker emitted by ``to_llm_input`` at each page boundary. Future Content +# Understanding service versions emit this same marker directly in the +# returned markdown (per ContentUnderstanding-Docs#249). When the helper sees +# any occurrence of this prefix in the input markdown it treats the service +# as having already paginated the content and skips its own injection to +# avoid duplicate markers. +_INPUT_PAGE_MARKER_PREFIX = "`` + markers at page boundaries when the service result does not already + include them. Internal telemetry messages such as ``LLMStats: ...`` + are filtered from the rendered ``rai_warnings`` front matter. + :param result: The ``AnalysisResult`` from a Content Understanding analyze operation. :type result: ~azure.ai.contentunderstanding.models.AnalysisResult :keyword include_fields: Whether to include structured fields in the @@ -379,7 +415,12 @@ def _render_content_block( def _add_page_markers(content: "DocumentContent", markdown: str) -> str: - """Add ```` markers to document markdown. + """Add ```` markers to document markdown. + + If *markdown* already contains ``\n*") - shifts: List[tuple] = [] # (original_pos, delta) + shifts: List[Tuple[int, int]] = [] # (original_pos, delta) for m in break_pattern.finditer(markdown): replacement_len = 2 # "\n\n" delta = m.end() - m.start() - replacement_len @@ -438,7 +481,7 @@ def _adjusted_offset(orig: int) -> int: for offset, page_num in markers: adj = _adjusted_offset(offset) parts.append(cleaned[prev:adj]) - parts.append(f"\n\n") + parts.append(f"{_INPUT_PAGE_MARKER_PREFIX} {page_num} -->\n\n") prev = adj parts.append(cleaned[prev:]) @@ -464,7 +507,7 @@ def _page_markers_from_breaks(markdown: str, content: "DocumentContent") -> str: page_num = start_page + i text = chunk.strip() if text: - parts.append(f"\n\n{text}") + parts.append(f"{_INPUT_PAGE_MARKER_PREFIX} {page_num} -->\n\n{text}") return "\n\n".join(parts) @@ -559,11 +602,18 @@ def _format_warnings( """ items: List[Dict[str, str]] = [] for w in warnings: + message = getattr(w, "message", None) + # Skip internal service telemetry strings (e.g. ``LLMStats: ...``) + # that occasionally leak into the warnings collection. These are + # not Responsible-AI warnings and would otherwise be rendered into + # the LLM-facing ``rai_warnings:`` block. + if message and message.lstrip().startswith(_TELEMETRY_MESSAGE_PREFIXES): + continue entry: Dict[str, str] = {} if getattr(w, "code", None): entry["code"] = w.code # type: ignore[assignment, union-attr] - if getattr(w, "message", None): - entry["message"] = w.message # type: ignore[assignment, union-attr] + if message: + entry["message"] = message if getattr(w, "target", None): entry["target"] = w.target # type: ignore[assignment, union-attr] if entry: diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/tests/samples/test_sample_to_llm_input.py b/sdk/contentunderstanding/azure-ai-contentunderstanding/tests/samples/test_sample_to_llm_input.py index b4132da1f426..321c83212110 100644 --- a/sdk/contentunderstanding/azure-ai-contentunderstanding/tests/samples/test_sample_to_llm_input.py +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/tests/samples/test_sample_to_llm_input.py @@ -147,16 +147,20 @@ def test_to_llm_input_multi_page_content_range(self, contentunderstanding_endpoi print(f"[PASS] to_llm_input output validated ({len(text)} chars, pages='2-3, 5' preserved)") # Page markers in the markdown body should use the original page numbers - # (, , ), not renumbered (1, 2, 3). - assert "" not in text, ( - "Page marker '' should not appear — we only requested pages 2-3, 5" + # (, , ), + # not renumbered (1, 2, 3). + assert "" not in text, ( + "Page marker '' should not appear — we only requested pages 2-3, 5" ) for expected_page in [2, 3, 5]: - assert f"" in text, ( - f"Page marker '' should appear in the markdown body. " + assert f"" in text, ( + f"Page marker '' should appear in the markdown body. " f"Output:\n{text[:800]}" ) - print("[PASS] Page markers verified: , , ") + print( + "[PASS] Page markers verified: , " + ", " + ) print("\n[SUCCESS] All test_to_llm_input_multi_page_content_range assertions passed") diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/tests/samples/test_sample_to_llm_input_async.py b/sdk/contentunderstanding/azure-ai-contentunderstanding/tests/samples/test_sample_to_llm_input_async.py index 36ec9791c4a5..4b566c8fa598 100644 --- a/sdk/contentunderstanding/azure-ai-contentunderstanding/tests/samples/test_sample_to_llm_input_async.py +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/tests/samples/test_sample_to_llm_input_async.py @@ -148,16 +148,20 @@ async def test_to_llm_input_multi_page_content_range_async(self, contentundersta print(f"[PASS] to_llm_input output validated ({len(text)} chars, pages='2-3, 5' preserved)") # Page markers in the markdown body should use the original page numbers - # (, , ), not renumbered (1, 2, 3). - assert "" not in text, ( - "Page marker '' should not appear — we only requested pages 2-3, 5" + # (, , ), + # not renumbered (1, 2, 3). + assert "" not in text, ( + "Page marker '' should not appear — we only requested pages 2-3, 5" ) for expected_page in [2, 3, 5]: - assert f"" in text, ( - f"Page marker '' should appear in the markdown body. " + assert f"" in text, ( + f"Page marker '' should appear in the markdown body. " f"Output:\n{text[:800]}" ) - print("[PASS] Page markers verified: , , ") + print( + "[PASS] Page markers verified: , " + ", " + ) await client.close() print("\n[SUCCESS] All test_to_llm_input_multi_page_content_range_async assertions passed") diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/tests/test_to_llm_input.py b/sdk/contentunderstanding/azure-ai-contentunderstanding/tests/test_to_llm_input.py index 2cd18729b67d..8845fc2088bc 100644 --- a/sdk/contentunderstanding/azure-ai-contentunderstanding/tests/test_to_llm_input.py +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/tests/test_to_llm_input.py @@ -287,8 +287,23 @@ def test_page_markers_from_spans(self): ], ) output = to_llm_input(_make_result([doc])) - assert "" in output - assert "" in output + assert "" in output + assert "" in output + + def test_page_markers_not_duplicated_when_service_provides_markers(self): + doc = DocumentContent( + kind="document", + markdown="\n\nFirst page text.\n\n\n\nSecond page text.", + start_page_number=1, + end_page_number=2, + pages=[ + DocumentPage(page_number=1, spans=[ContentSpan(offset=0, length=47)]), + DocumentPage(page_number=2, spans=[ContentSpan(offset=49, length=48)]), + ], + ) + output = to_llm_input(_make_result([doc])) + assert output.count("") == 1 + assert output.count("") == 1 def test_page_markers_from_pagebreak_fallback(self): doc = DocumentContent( @@ -298,8 +313,8 @@ def test_page_markers_from_pagebreak_fallback(self): end_page_number=2, ) output = to_llm_input(_make_result([doc])) - assert "" in output - assert "" in output + assert "" in output + assert "" in output assert "" not in output def test_page_markers_respect_start_page_number(self): @@ -311,8 +326,8 @@ def test_page_markers_respect_start_page_number(self): end_page_number=4, ) output = to_llm_input(_make_result([doc])) - assert "" in output - assert "" in output + assert "" in output + assert "" in output def test_pages_single_page_format(self): doc = _make_invoice_doc(start_page_number=1, end_page_number=1) @@ -867,6 +882,67 @@ def test_warnings_present_regardless_of_include_flags(self): output = to_llm_input(result, include_fields=False, include_markdown=False) assert "rai_warnings:" in output + def test_llm_stats_warning_filtered_from_rai_warnings(self): + from azure.core.exceptions import ODataV4Format + doc = _make_invoice_doc() + telemetry_warning = ODataV4Format( + {"code": "Telemetry", "message": "LLMStats: completion calls: 2; embedding calls: 1"} + ) + real_warning = ODataV4Format({"code": "ContentWarning", "message": "Potentially sensitive content."}) + result = AnalysisResult(contents=[doc], warnings=[telemetry_warning, real_warning]) + + output = to_llm_input(result) + + assert "rai_warnings:" in output + assert "LLMStats:" not in output + assert "Potentially sensitive content." in output + + def test_llm_stats_warning_only_omits_rai_warnings_block(self): + from azure.core.exceptions import ODataV4Format + doc = _make_invoice_doc() + warning = ODataV4Format({"code": "Telemetry", "message": "LLMStats: completion latency: 7.71s"}) + result = AnalysisResult(contents=[doc], warnings=[warning]) + + output = to_llm_input(result) + + assert "rai_warnings:" not in output + assert "LLMStats:" not in output + + def test_llm_stats_filter_is_case_sensitive(self): + from azure.core.exceptions import ODataV4Format + doc = _make_invoice_doc() + warning = ODataV4Format({"code": "ContentWarning", "message": "llmstats: keep as a real warning"}) + result = AnalysisResult(contents=[doc], warnings=[warning]) + + output = to_llm_input(result) + + assert "rai_warnings:" in output + assert "llmstats: keep as a real warning" in output + + def test_llm_stats_text_in_markdown_body_is_preserved(self): + from azure.core.exceptions import ODataV4Format + body_text = "A log excerpt:\n- LLMStats: keep this body text" + doc = _make_invoice_doc(markdown=body_text) + warning = ODataV4Format({"code": "Telemetry", "message": "LLMStats: remove this warning text"}) + result = AnalysisResult(contents=[doc], warnings=[warning]) + + output = to_llm_input(result) + + assert "rai_warnings:" not in output + assert "LLMStats: keep this body text" in output + assert "LLMStats: remove this warning text" not in output + + def test_llm_stats_warning_filtered_with_leading_whitespace(self): + from azure.core.exceptions import ODataV4Format + doc = _make_invoice_doc() + warning = ODataV4Format({"code": "Telemetry", "message": " LLMStats: completion calls: 2"}) + result = AnalysisResult(contents=[doc], warnings=[warning]) + + output = to_llm_input(result) + + assert "rai_warnings:" not in output + assert "LLMStats:" not in output + def test_empty_string_field_value_quoted(self): doc = DocumentContent( kind="document", @@ -1029,9 +1105,9 @@ def test_multipage_doc_strips_pagebreak_with_spans(self): ) output = to_llm_input(_make_result([doc])) assert "" not in output - assert "" in output - assert "" in output - assert "" in output + assert "" in output + assert "" in output + assert "" in output assert "Page 1 content." in output assert "Page 2 content." in output assert "Page 3 content." in output @@ -1048,7 +1124,7 @@ def test_image_with_empty_page_spans_falls_back(self): ) output = to_llm_input(_make_result([doc])) # Should fall back to PageBreak method, which adds page 1 marker - assert "" in output + assert "" in output assert "![image](pages/1)" in output def test_document_search_png_single_page_with_spans(self): @@ -1063,7 +1139,7 @@ def test_document_search_png_single_page_with_spans(self): pages=[DocumentPage(page_number=1, spans=[ContentSpan(offset=0, length=len(markdown))])], ) output = to_llm_input(_make_result([doc])) - assert "" in output + assert "" in output assert "IAN HANSSON" in output assert "Summary: A resume document." in output @@ -1082,8 +1158,8 @@ def test_prebuilt_read_no_fields(self): output = to_llm_input(_make_result([doc])) assert "contentType: document" in output assert "fields:" not in output - assert "" in output - assert "" in output + assert "" in output + assert "" in output def test_metadata_keys_with_yaml_special_chars(self): """Metadata keys with YAML-special characters must be quoted to produce valid YAML.""" From 8f725ddda15a2ab8d1886a8e5addcff94e384a07 Mon Sep 17 00:00:00 2001 From: chienyuanchang Date: Wed, 3 Jun 2026 13:41:54 -0700 Subject: [PATCH 2/7] update version --- .../azure-ai-contentunderstanding/CHANGELOG.md | 12 ++++++++---- .../azure-ai-contentunderstanding/README.md | 1 + .../azure/ai/contentunderstanding/_version.py | 2 +- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/CHANGELOG.md b/sdk/contentunderstanding/azure-ai-contentunderstanding/CHANGELOG.md index 0b3ebbab185f..db0b86a859c8 100644 --- a/sdk/contentunderstanding/azure-ai-contentunderstanding/CHANGELOG.md +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/CHANGELOG.md @@ -1,14 +1,18 @@ # Release History +## 1.2.0b2 (Unreleased) + +### Bugs Fixed +- Filtered service-emitted `LLMStats:` telemetry entries from the rendered `rai_warnings` front matter. + +### Other Changes +- Updated `to_llm_input` page markers from `` to `` and avoided duplicate marker injection when the service markdown already includes `InputPageNumber` markers. + ## 1.2.0b1 (2026-04-28) ### Features Added - Added `to_llm_input` helper function that converts `AnalysisResult` objects into LLM-friendly text with YAML front matter and markdown content. Supports documents, audio/video, and classification hierarchies. -### Bugs Fixed -- Updated `to_llm_input` page markers from `` to `` and avoided duplicate marker injection when the service markdown already includes `InputPageNumber` markers. -- Filtered service-emitted `LLMStats:` telemetry entries from the rendered `rai_warnings` front matter. - ## 1.1.0 (2026-04-20) ### Features Added diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/README.md b/sdk/contentunderstanding/azure-ai-contentunderstanding/README.md index a50a10002bf4..59125b208808 100644 --- a/sdk/contentunderstanding/azure-ai-contentunderstanding/README.md +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/README.md @@ -59,6 +59,7 @@ This table shows the relationship between SDK versions and supported API service | SDK version | Supported API service version | | ----------- | ----------------------------- | +| 1.2.0b2 | 2025-11-01 | | 1.2.0b1 | 2025-11-01 | | 1.1.0 | 2025-11-01 | | 1.0.1 | 2025-11-01 | diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/azure/ai/contentunderstanding/_version.py b/sdk/contentunderstanding/azure-ai-contentunderstanding/azure/ai/contentunderstanding/_version.py index 5bf479b145f7..a8cca866f40a 100644 --- a/sdk/contentunderstanding/azure-ai-contentunderstanding/azure/ai/contentunderstanding/_version.py +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/azure/ai/contentunderstanding/_version.py @@ -6,4 +6,4 @@ # Changes may cause incorrect behavior and will be lost if the code is regenerated. # -------------------------------------------------------------------------- -VERSION = "1.2.0b1" +VERSION = "1.2.0b2" From 6a3df4094287667bf551faf0132f0fe57873ca5e Mon Sep 17 00:00:00 2001 From: chienyuanchang Date: Mon, 8 Jun 2026 17:14:53 -0700 Subject: [PATCH 3/7] Add lowercase 'llmstats' to cspell ignoreWords for case-sensitivity test --- .../azure-ai-contentunderstanding/cspell.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/cspell.json b/sdk/contentunderstanding/azure-ai-contentunderstanding/cspell.json index 020055152848..219ffa033be6 100644 --- a/sdk/contentunderstanding/azure-ai-contentunderstanding/cspell.json +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/cspell.json @@ -2,6 +2,7 @@ "ignoreWords": [ "Agentic", "chartjs", + "llmstats", "redef", "UPCA", "UPCE", @@ -11,6 +12,6 @@ "ignorePaths": [ "sdk/contentunderstanding/azure-ai-contentunderstanding/samples/sample_files/training_samples/*.json" ], - "_comment": "ignoreWords: UPCA/UPCE/upca/upce are barcode types from _enums.py and _models.py as OCR Barcode types standardized in the ISO/IEC 15415:2019 standard; Agentic is a term for agentic AI; chartjs refers to Chart.js format" + "_comment": "ignoreWords: UPCA/UPCE/upca/upce are barcode types from _enums.py and _models.py as OCR Barcode types standardized in the ISO/IEC 15415:2019 standard; Agentic is a term for agentic AI; chartjs refers to Chart.js format; llmstats is a lowercase variant intentionally used in test_to_llm_input.py to assert case-sensitive telemetry filtering" } From 4a1ea7d1ff7673fec810a6f34bb26757e0919f35 Mon Sep 17 00:00:00 2001 From: chienyuanchang Date: Tue, 9 Jun 2026 19:27:25 -0700 Subject: [PATCH 4/7] Expand to_llm_input docstring: explain InputPageNumber and content_range --- .../azure/ai/contentunderstanding/_helpers.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/azure/ai/contentunderstanding/_helpers.py b/sdk/contentunderstanding/azure-ai-contentunderstanding/azure/ai/contentunderstanding/_helpers.py index 7156c1effae4..9736805fce83 100644 --- a/sdk/contentunderstanding/azure-ai-contentunderstanding/azure/ai/contentunderstanding/_helpers.py +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/azure/ai/contentunderstanding/_helpers.py @@ -98,8 +98,16 @@ def to_llm_input( For document content, the helper emits ```` markers at page boundaries when the service result does not already - include them. Internal telemetry messages such as ``LLMStats: ...`` - are filtered from the rendered ``rai_warnings`` front matter. + include them. ``N`` is the **original 1-based page number from the + source document** (i.e., the page index in the analyzed PDF), not a + counter that restarts at 1 for each call. This is important when the + analyze request specifies a ``content_range`` (e.g., ``"2-3, 5"``): + the markers in the output will read ``InputPageNumber: 2``, ``3``, + ``5`` \u2014 not ``1``, ``2``, ``3``. Downstream consumers (RAG indexers, + page-citation prompts) can rely on the marker value to cite the + correct source page even when only a subset of pages was analyzed. + Internal telemetry messages such as ``LLMStats: ...`` are filtered + from the rendered ``rai_warnings`` front matter. :param result: The ``AnalysisResult`` from a Content Understanding analyze operation. :type result: ~azure.ai.contentunderstanding.models.AnalysisResult From 6794276e169cce4c622c01c1c8759b1b41ab5e12 Mon Sep 17 00:00:00 2001 From: chienyuanchang Date: Tue, 9 Jun 2026 19:47:44 -0700 Subject: [PATCH 5/7] Add README callout explaining InputPageNumber and content range --- .../azure-ai-contentunderstanding/README.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/README.md b/sdk/contentunderstanding/azure-ai-contentunderstanding/README.md index 59125b208808..11831acfc73d 100644 --- a/sdk/contentunderstanding/azure-ai-contentunderstanding/README.md +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/README.md @@ -558,7 +558,7 @@ print(text) # figure illustrating monthly values, and describes the AI Document # Intelligence service... # --- -# +# # # ==This is title== # ## 1. Text # [Latin](https://en.wikipedia.org/wiki/Latin) refers to an ancient Italic language... @@ -573,6 +573,16 @@ print(text) # ... ``` +> **About ``** +> The helper emits `` markers at page boundaries in +> the markdown body. `N` is the **original 1-based page number from the source +> document** (i.e., the page index in the analyzed PDF), not a counter that +> restarts at 1 for each call. This matters when the analyze request specifies +> a `content_range` (e.g., `"2-3,5"`): the markers will read +> `InputPageNumber: 2`, `3`, `5` — not `1`, `2`, `3`. Downstream consumers +> (RAG indexers, page-citation prompts) can rely on the marker value to cite +> the correct source page even when only a subset of pages was analyzed. + See the [advanced sample][python_cu_sample_to_llm_input] for output options (fields-only, markdown-only, custom metadata), multi-page content ranges, and multi-segment video. From e538d4beb6451a0dc8ecce7506dbb4f2bc031b88 Mon Sep 17 00:00:00 2001 From: chienyuanchang Date: Tue, 9 Jun 2026 20:19:34 -0700 Subject: [PATCH 6/7] Expand README InputPageNumber callout with a content-range example --- .../azure-ai-contentunderstanding/README.md | 37 ++++++++++++++++--- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/README.md b/sdk/contentunderstanding/azure-ai-contentunderstanding/README.md index 11831acfc73d..0f4d4fc0ff41 100644 --- a/sdk/contentunderstanding/azure-ai-contentunderstanding/README.md +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/README.md @@ -574,14 +574,41 @@ print(text) ``` > **About ``** +> > The helper emits `` markers at page boundaries in > the markdown body. `N` is the **original 1-based page number from the source > document** (i.e., the page index in the analyzed PDF), not a counter that -> restarts at 1 for each call. This matters when the analyze request specifies -> a `content_range` (e.g., `"2-3,5"`): the markers will read -> `InputPageNumber: 2`, `3`, `5` — not `1`, `2`, `3`. Downstream consumers -> (RAG indexers, page-citation prompts) can rely on the marker value to cite -> the correct source page even when only a subset of pages was analyzed. +> restarts at 1 for each call. Downstream consumers (RAG indexers, page-citation +> prompts) can rely on the marker value to cite the correct source page even +> when only a subset of pages was analyzed. +> +> **Why this matters when a page range is specified** +> +> Use `content_range` on the analyze input to analyze only a subset of pages in +> a multi-page document. The markers in the rendered output preserve the +> original page identity: +> +> ```python +> # Analyze pages 2-3 and page 5 of a 10-page PDF. +> poller = client.begin_analyze( +> analyzer_id="prebuilt-documentSearch", +> inputs=[AnalysisInput(url=multi_page_url, content_range="2-3,5")], +> ) +> result = poller.result() +> text = to_llm_input(result) +> # Output contains markers for the *original* page numbers, not 1, 2, 3: +> # pages: 2-3, 5 +> # ... +> # +> # ...page 2 content... +> # +> # ...page 3 content... +> # +> # ...page 5 content... +> ``` +> +> An LLM or RAG indexer can therefore cite "see page 5" with the correct page +> number, even though page 5 is the *third* segment in the response. See the [advanced sample][python_cu_sample_to_llm_input] for output options (fields-only, markdown-only, custom metadata), multi-page content ranges, and multi-segment video. From 02ca9c21b74b2646f76f42599142ddfbade16e94 Mon Sep 17 00:00:00 2001 From: chienyuanchang Date: Tue, 9 Jun 2026 20:42:02 -0700 Subject: [PATCH 7/7] Normalize endpoint in create_client_from_credential overrides --- .../tests/testpreparer.py | 10 ++++++++++ .../tests/testpreparer_async.py | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/tests/testpreparer.py b/sdk/contentunderstanding/azure-ai-contentunderstanding/tests/testpreparer.py index af4e29821e84..48bd90826588 100644 --- a/sdk/contentunderstanding/azure-ai-contentunderstanding/tests/testpreparer.py +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/tests/testpreparer.py @@ -37,6 +37,16 @@ def create_client(self, endpoint: str) -> ContentUnderstandingClient: ), ) + def create_client_from_credential(self, client_class, *, endpoint=None, **kwargs): + # Mirror create_client(): strip trailing slashes so the generated URL template + # "{endpoint}/contentunderstanding" never produces "//contentunderstanding". + # Tests that build a second client for a different resource (e.g. grant_copy_auth + # target client) take this path with a raw env-var endpoint, so they need the same + # normalization the primary create_client() already performs. + if isinstance(endpoint, str): + endpoint = endpoint.rstrip("/") + return super().create_client_from_credential(client_class, endpoint=endpoint, **kwargs) + ContentUnderstandingPreparer = functools.partial( PowerShellPreparer, diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/tests/testpreparer_async.py b/sdk/contentunderstanding/azure-ai-contentunderstanding/tests/testpreparer_async.py index 7b13ec166d01..53637b2a062b 100644 --- a/sdk/contentunderstanding/azure-ai-contentunderstanding/tests/testpreparer_async.py +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/tests/testpreparer_async.py @@ -38,6 +38,16 @@ def create_async_client(self, endpoint: str) -> ContentUnderstandingClient: ), ) + def create_client_from_credential(self, client_class, *, endpoint=None, **kwargs): + # Mirror create_async_client(): strip trailing slashes so the generated URL template + # "{endpoint}/contentunderstanding" never produces "//contentunderstanding". + # Tests that build a second client for a different resource (e.g. grant_copy_auth + # target client) take this path with a raw env-var endpoint, so they need the same + # normalization the primary create_async_client() already performs. + if isinstance(endpoint, str): + endpoint = endpoint.rstrip("/") + return super().create_client_from_credential(client_class, endpoint=endpoint, **kwargs) + ContentUnderstandingPreparer = functools.partial( PowerShellPreparer,