From ae811043e9a3245f68e457903df77c49799b11fd Mon Sep 17 00:00:00 2001 From: Hongsheng Liu Date: Mon, 10 Aug 2026 19:13:52 +0800 Subject: [PATCH] docs: keep shared task examples in navigation (#5987) Signed-off-by: Hongsheng Liu --- docs/.nav.yml | 18 +--- docs/mkdocs/hooks/generate_examples.py | 69 ++++++--------- tests/docs/test_generate_examples.py | 113 ++----------------------- 3 files changed, 35 insertions(+), 165 deletions(-) diff --git a/docs/.nav.yml b/docs/.nav.yml index fc11bb838..0da0c779c 100644 --- a/docs/.nav.yml +++ b/docs/.nav.yml @@ -16,31 +16,21 @@ nav: - Examples: - examples/README.md - Offline Inference: - - GLM-Image Multistage End-to-End Inference: user_guide/examples/offline_inference/glm_image.md - - HunyuanImage-3.0 Image-to-Text Inference: user_guide/examples/offline_inference/hunyuan_image3.md - Image-To-Image: user_guide/examples/offline_inference/image_to_image.md - Image-To-Video: user_guide/examples/offline_inference/image_to_video.md - - InternVLA-A1: user_guide/examples/offline_inference/internvla_a1.md - - MiMo-Audio: user_guide/examples/offline_inference/mimo_audio.md - - Qwen2.5-Omni: user_guide/examples/offline_inference/qwen2_5_omni.md - - Qwen3-Omni: user_guide/examples/offline_inference/qwen3_omni.md + - Speech-To-Video: user_guide/examples/offline_inference/speech_to_video.md - Text-To-Audio: user_guide/examples/offline_inference/text_to_audio.md - Text-To-Image: user_guide/examples/offline_inference/text_to_image.md - Text-To-Speech: user_guide/examples/offline_inference/text_to_speech.md - Text-To-Video: user_guide/examples/offline_inference/text_to_video.md + - X-To-Text: user_guide/examples/offline_inference/x_to_text.md - X-To-Video-Audio: user_guide/examples/offline_inference/x_to_video_audio.md - Online Serving: - - BAGEL-7B-MoT: user_guide/examples/online_serving/bagel.md - - vLLM-Omni Helm Chart: user_guide/examples/online_serving/chart-helm.md - - Diffusers Backend Adapter: user_guide/examples/online_serving/diffusers_pipeline_adapter.md - - GLM-Image Online Serving: user_guide/examples/online_serving/glm_image.md - Image-To-Image: user_guide/examples/online_serving/image_to_image.md - Image-To-Video: user_guide/examples/online_serving/image_to_video.md - - MiMo-Audio: user_guide/examples/online_serving/mimo_audio.md - - Qwen2.5-Omni: user_guide/examples/online_serving/qwen2_5_omni.md - - Qwen3-Omni: user_guide/examples/online_serving/qwen3_omni.md - - Text-To-Speech: user_guide/examples/online_serving/text_to_speech.md + - Speech-To-Video: user_guide/examples/online_serving/speech_to_video.md - Text-To-Image: user_guide/examples/online_serving/text_to_image.md + - Text-To-Speech: user_guide/examples/online_serving/text_to_speech.md - Text-To-Video: user_guide/examples/online_serving/text_to_video.md - General: - usage/* diff --git a/docs/mkdocs/hooks/generate_examples.py b/docs/mkdocs/hooks/generate_examples.py index f3f96eb2d..8ddb63561 100644 --- a/docs/mkdocs/hooks/generate_examples.py +++ b/docs/mkdocs/hooks/generate_examples.py @@ -24,15 +24,22 @@ SERVING_MODE_TITLES = { "online_serving": "Online serving", } -# Generated example pages keep their source-derived URLs even when their -# navigation category changes. Quantization helpers are feature documentation, -# not user-serving examples, so keep them under the existing Features / -# Quantization section instead of creating a second top-level Quantization -# category under Examples. -EXAMPLE_NAV_TARGETS = { - "quantization": "quantization", -} -GENERATED_QUANTIZATION_EXAMPLES_TITLE = "Generated Examples" +# Keep the Examples navigation focused on reusable modality/task entry points. +# Model-specific scripts remain available under ``examples/`` and are surfaced +# from these shared pages instead of getting one page per model. +GENERAL_EXAMPLE_SLUGS = frozenset( + { + "image_to_image", + "image_to_video", + "speech_to_video", + "text_to_audio", + "text_to_image", + "text_to_speech", + "text_to_video", + "x_to_text", + "x_to_video_audio", + } +) def load_model_display_names() -> dict[str, str]: @@ -56,6 +63,13 @@ def load_model_display_names() -> dict[str, str]: MODEL_DISPLAY_NAMES = load_model_display_names() +def is_general_example(example: "Example") -> bool: + """Return whether an example should get a generated serving page.""" + if example.category not in SERVING_MODE_TITLES: + return True + return example.path.stem in GENERAL_EXAMPLE_SLUGS + + def fix_case(text: str) -> str: subs = { "api": "API", @@ -358,12 +372,9 @@ def update_nav_file(examples: list[Example]): items.append({example.nav_title: rel_path.as_posix()}) return items - # Build the new Examples section - start with preserved items. Categories - # not explicitly routed elsewhere retain the historical Examples behavior. + # Build the new Examples section - start with preserved items. examples_section = preserved_items.copy() for category in sorted(examples_by_category.keys()): - if EXAMPLE_NAV_TARGETS.get(category, "examples") != "examples": - continue items = category_items(category) if items: category_title = fix_case(category.replace("_", " ").title()) @@ -372,36 +383,6 @@ def update_nav_file(examples: list[Example]): # Update the nav structure nav_list[user_guide_idx]["User Guide"][examples_idx]["Examples"] = examples_section - features_content = next( - (item["Features"] for item in user_guide_content if isinstance(item, dict) and "Features" in item), - None, - ) - quantization_content = None - if isinstance(features_content, list): - quantization_content = next( - (item["Quantization"] for item in features_content if isinstance(item, dict) and "Quantization" in item), - None, - ) - - if isinstance(quantization_content, list): - # Remove only the group owned by this hook so repeated builds are - # idempotent and hand-authored Quantization navigation is retained. - quantization_content[:] = [ - item - for item in quantization_content - if not (isinstance(item, dict) and GENERATED_QUANTIZATION_EXAMPLES_TITLE in item) - ] - - quantization_examples = examples_by_category.get("quantization") - if quantization_examples: - if not isinstance(quantization_content, list): - logger.warning( - "Could not find 'User Guide' -> 'Features' -> 'Quantization' section; " - "omitting generated quantization examples from navigation" - ) - else: - quantization_content.append({GENERATED_QUANTIZATION_EXAMPLES_TITLE: category_items("quantization")}) - # Write back to file nav_data["nav"] = nav_list with open(NAV_FILE, "w", encoding="utf-8") as f: @@ -432,6 +413,8 @@ def on_startup(command: Literal["build", "gh-deploy", "serve"], dirty: bool): for path in category.glob("*/*.md"): examples.append(Example(path.parent, category.stem)) + examples = [example for example in examples if is_general_example(example)] + # Generate the example documentation for example in sorted(examples, key=lambda e: e.path.stem): example_name = f"{example.path.stem}.md" diff --git a/tests/docs/test_generate_examples.py b/tests/docs/test_generate_examples.py index 34d7045f9..9883e387d 100644 --- a/tests/docs/test_generate_examples.py +++ b/tests/docs/test_generate_examples.py @@ -6,7 +6,6 @@ from pathlib import Path from types import SimpleNamespace import pytest -import yaml pytestmark = [pytest.mark.core_model, pytest.mark.cpu] @@ -47,111 +46,9 @@ def test_model_display_names_require_string_mapping(tmp_path, monkeypatch): generate_examples.load_model_display_names() -def test_quantization_examples_keep_source_urls_under_feature_navigation(tmp_path, monkeypatch): - nav_file = tmp_path / "docs" / ".nav.yml" - nav_file.parent.mkdir(parents=True) - nav_file.write_text( - yaml.safe_dump( - { - "nav": [ - { - "User Guide": [ - { - "Examples": [ - "examples/README.md", - {"Offline Inference": [{"Old": "user_guide/examples/offline_inference/old.md"}]}, - ] - }, - {"Features": [{"Quantization": [{"Overview": "user_guide/quantization/overview.md"}]}]}, - ] - } - ] - }, - sort_keys=False, - ), - encoding="utf-8", - ) +def test_only_general_serving_examples_get_generated_pages(): + general_example = SimpleNamespace(category="offline_inference", path=Path("text_to_image")) + model_example = SimpleNamespace(category="offline_inference", path=Path("qwen3_omni")) - monkeypatch.setattr(generate_examples, "ROOT_DIR", tmp_path) - monkeypatch.setattr(generate_examples, "NAV_FILE", nav_file) - monkeypatch.setattr(generate_examples, "EXAMPLE_DOC_DIR", tmp_path / "docs/user_guide/examples") - - quantization_example = SimpleNamespace( - category="quantization", - path=tmp_path / "examples/quantization/check_modelopt_fp8_export.py", - nav_title="Check Modelopt FP8 Export", - ) - offline_example = SimpleNamespace( - category="offline_inference", - path=tmp_path / "examples/offline_inference/demo.py", - nav_title="Demo", - ) - - generate_examples.update_nav_file([quantization_example, offline_example]) - generate_examples.update_nav_file([quantization_example, offline_example]) - - nav_data = yaml.safe_load(nav_file.read_text(encoding="utf-8")) - user_guide = nav_data["nav"][0]["User Guide"] - examples = next(item["Examples"] for item in user_guide if "Examples" in item) - features = next(item["Features"] for item in user_guide if "Features" in item) - quantization = next(item["Quantization"] for item in features if "Quantization" in item) - - assert not any(isinstance(item, dict) and "Quantization" in item for item in examples) - generated_examples = [ - item[generate_examples.GENERATED_QUANTIZATION_EXAMPLES_TITLE] - for item in quantization - if generate_examples.GENERATED_QUANTIZATION_EXAMPLES_TITLE in item - ] - assert len(generated_examples) == 1 - assert generated_examples[0] == [ - {"Check Modelopt FP8 Export": "user_guide/examples/quantization/check_modelopt_fp8_export.md"} - ] - - # A later build without quantization examples must not leave a stale - # generated group in the hand-authored Quantization section. - generate_examples.update_nav_file([offline_example]) - nav_data = yaml.safe_load(nav_file.read_text(encoding="utf-8")) - user_guide = nav_data["nav"][0]["User Guide"] - features = next(item["Features"] for item in user_guide if "Features" in item) - quantization = next(item["Quantization"] for item in features if "Quantization" in item) - assert not any( - isinstance(item, dict) and generate_examples.GENERATED_QUANTIZATION_EXAMPLES_TITLE in item - for item in quantization - ) - - -def test_quantization_examples_are_not_added_to_examples_without_feature_target(tmp_path, monkeypatch, caplog): - nav_file = tmp_path / "docs" / ".nav.yml" - nav_file.parent.mkdir(parents=True) - nav_file.write_text( - yaml.safe_dump( - { - "nav": [ - { - "User Guide": [ - {"Examples": ["examples/README.md"]}, - ] - } - ] - }, - sort_keys=False, - ), - encoding="utf-8", - ) - - monkeypatch.setattr(generate_examples, "ROOT_DIR", tmp_path) - monkeypatch.setattr(generate_examples, "NAV_FILE", nav_file) - monkeypatch.setattr(generate_examples, "EXAMPLE_DOC_DIR", tmp_path / "docs/user_guide/examples") - - quantization_example = SimpleNamespace( - category="quantization", - path=tmp_path / "examples/quantization/check_modelopt_fp8_export.py", - nav_title="Check Modelopt FP8 Export", - ) - - generate_examples.update_nav_file([quantization_example]) - - nav_data = yaml.safe_load(nav_file.read_text(encoding="utf-8")) - examples = nav_data["nav"][0]["User Guide"][0]["Examples"] - assert not any(isinstance(item, dict) and "Quantization" in item for item in examples) - assert "omitting generated quantization examples from navigation" in caplog.text + assert generate_examples.is_general_example(general_example) + assert not generate_examples.is_general_example(model_example)