From 5a2b449d3e05795e2dfab6e59683367394ce137a Mon Sep 17 00:00:00 2001 From: zhifu gao Date: Tue, 8 Sep 2026 18:28:55 +0000 Subject: [PATCH 1/2] docs: distinguish model checkpoints and serving interfaces Signed-off-by: zhifu gao --- docs/README.md | 3 +- docs/model_selection.md | 28 ++++++++- docs/model_selection_ja.md | 23 ++++++- docs/model_selection_ko.md | 23 ++++++- docs/model_selection_zh.md | 25 +++++++- tests/test_vllm_model_source_docs.py | 62 +++++++++++++++++++ .../browser/model-selection-tags.spec.ts | 45 ++++++++++++++ 7 files changed, 202 insertions(+), 7 deletions(-) diff --git a/docs/README.md b/docs/README.md index 86a86d546..7780bd969 100644 --- a/docs/README.md +++ b/docs/README.md @@ -33,7 +33,8 @@ | Distinguish speaker vectors, anonymous labels and emotion tags | [Speakers and emotion tags](speaker_emotion.md) / [中文](speaker_emotion_zh.md) | | Transcribe and diarize with third-party MOSS | [MOSS](moss_transcribe_diarize.md) / [中文](moss_transcribe_diarize_zh.md) | | Accelerate with the FunASR vLLM split engine | [vLLM](vllm_guide.md) / [中文](vllm_guide_zh.md) | -| Evaluate native vLLM serving | [Validation record](vllm_native_funasr_validation.md) | +| Evaluate official native vLLM serving | [Official checkpoint validation](vllm_official_native_validation.md) / [中文](vllm_official_native_validation_zh.md) | +| Inspect the historical community native vLLM run (2026-08-13) | [Historical community record](vllm_native_funasr_validation.md) | | Deploy llama.cpp, TensorRT, Docker or Kubernetes | [Deployment manuals](https://www.funasr.com/en/deploy/) | | Choose a service entry point and runtime SDK | [Runtime guide](../runtime/readme.md) / [中文](../runtime/readme_cn.md) | | Select a development or serving container | [Docker](installation/docker.md) / [中文](installation/docker_zh.md) | diff --git a/docs/model_selection.md b/docs/model_selection.md index dca8a2f4e..ac706a41d 100644 --- a/docs/model_selection.md +++ b/docs/model_selection.md @@ -42,7 +42,7 @@ Switch to Paraformer when your workload is Mandarin-only and you want character- | Fast multilingual private transcription | SenseVoice-Small | Strong default with ASR, emotion tags, audio event tags, and CPU viability. | [README quick start](../README.md#quick-start) | | Mandarin production ASR | Paraformer-Large | Mature Chinese ASR path with VAD and punctuation. | [Tutorial](./tutorial/README.md) | | English-only route in the OpenAI API example | `paraformer-en` alias | Smaller English route for API compatibility checks. | [OpenAI API example](../examples/openai_api/) | -| LLM-based ASR or Chinese/English/Japanese + dialect experiments | Fun-ASR-Nano | LLM-based model path; use vLLM when decoder throughput matters. | [vLLM guide](./vllm_guide.md) | +| LLM-based ASR or Chinese/English/Japanese + dialect experiments | Fun-ASR-Nano | Evaluate the Python path first; split-engine and native vLLM use different loading contracts. | [Choose a vLLM path](#vllm-checkpoint-paths) | | Offline long-form ASR with anonymous diarization | MOSS-Transcribe-Diarize | One offline request returns transcription, timestamps, and per-recording anonymous speaker labels; it does not identify known people and needs no external VAD or speaker model. | [MOSS deployment guide](./moss_transcribe_diarize.md) | | Live captions or call-center streams | Runtime WebSocket service | Designed for long-lived streaming sessions and partial results. | [Runtime service docs](../runtime/readme.md) | | Batch archive processing | SenseVoice-Small or Paraformer-Large | Stable offline transcription path; caller owns manifests, retries, and logs. | [Batch ASR example](../examples/batch_asr_improved.py) | @@ -56,6 +56,13 @@ The `examples/openai_api` server exposes short aliases so application teams do n - **`paraformer`** uses `paraformer-zh` with VAD and punctuation for a Mandarin-oriented route. - **`paraformer-en`** uses `paraformer-en` with VAD for English transcription in OpenAI-style clients. - **`fun-asr-nano`** uses `FunAudioLLM/Fun-ASR-Nano-2512` for evaluating Chinese, English, Japanese, and Chinese dialect/accent coverage. Select a compatible runtime when evaluating vLLM acceleration. +- **`moss-transcribe-diarize`** uses the third-party `OpenMOSS-Team/MOSS-Transcribe-Diarize` model for offline transcription and anonymous per-recording speaker labels. Prepare its separate dependencies and reviewed remote code using the [MOSS guide](./moss_transcribe_diarize.md); request `verbose_json` for structured segments. It does not require an external VAD/speaker model and does not identify known people. + +These aliases describe [the example server](../examples/openai_api/server.py), +which loads `AutoModel`. They do not configure native vLLM or automatically select +`AutoModelVLLM`. The packaged `funasr-server` has a separate loader and backend +selection; do not copy an alias or a performance result between services without +checking the corresponding [HTTP guide](../examples/openai_api/README.md). The example HTTP service cleans both top-level `text` and segment `text` in `verbose_json`; that format does not restore emotion/event tags. If you need @@ -82,10 +89,27 @@ For SDK, JavaScript, workflow, Postman, OpenAPI, Docker, and Kubernetes paths, s | Repeatable local container demo | Docker Compose API | CPU-first smoke test; adapt the image before using CUDA. | | Internal cluster service | Kubernetes API template | Private `ClusterIP`, persistent model cache, `/health` probes, and port-forward smoke test. | | Live audio | Runtime WebSocket service | Validate chunk size, VAD, endpointing, reconnects, and client backpressure with real audio. | -| LLM-based ASR throughput | vLLM path for Fun-ASR-Nano | vLLM accelerates autoregressive decoding; it does not apply to non-autoregressive Paraformer. | +| LLM-based ASR throughput | Choose split-engine or native vLLM below | Match the checkpoint, loading API and tested environment; this is not a Paraformer backend. | See the [deployment matrix](./deployment_matrix.md) when you are choosing between these paths. + + +## Choose the vLLM Checkpoint and Interface + +| Path | Checkpoint and interface | Read next | +| --- | --- | --- | +| FunASR split-engine | Base `FunAudioLLM/Fun-ASR-Nano-2512` assets through `AutoModelVLLM`; FunASR handles the audio side and vLLM the decoder. | [Split-engine preparation and limits](./vllm_guide.md) | +| Official native vLLM | Converted `FunAudioLLM/Fun-ASR-Nano-2512-vllm` snapshot through vLLM's native model implementation and `/v1/audio/transcriptions`. Not an `AutoModelVLLM` load. | [Official functional validation](./vllm_official_native_validation.md) | +| Historical community native vLLM | Community `allendou/Fun-ASR-Nano-2512-vllm`, tested on 2026-08-13. Its timings belong to that checkpoint and environment. | [Historical community record](./vllm_native_funasr_validation.md) | + +The official record pins a model revision and an existing environment; it is not +a clean-install recipe, a sustained-load benchmark, or proof of `/v1/realtime` +streaming. Do not reuse the historical community timings for the official model. +For MOSS, follow its own deployment guide: the Nano checkpoints and validation +above do not establish MOSS runtime compatibility. Choose model, checkpoint, +interface and environment together before testing your own workload. + ## Benchmark before committing Do not choose a model from a single clean demo file. Use a small representative set first: diff --git a/docs/model_selection_ja.md b/docs/model_selection_ja.md index 27f4e8d1f..0a1489a82 100644 --- a/docs/model_selection_ja.md +++ b/docs/model_selection_ja.md @@ -29,7 +29,7 @@ result = model.generate(input="meeting.wav") | 高速な多言語プライベート文字起こし | SenseVoice-Small | ASR、感情タグ、音声イベントタグ、CPU/GPU の扱いやすさがそろった標準ルート。 | [README quick start](../README_ja.md#クイックスタート) | | 中国語中心の本番 ASR | Paraformer-Large | VAD と句読点復元を組み合わせた成熟した中国語 ASR ルート。 | [Tutorial](./tutorial/README.md) | | OpenAI API 例の英語ルート | `paraformer-en` alias | OpenAI-style client で互換性を確認しやすい軽量な英語ルート。 | [OpenAI API example](../examples/openai_api/README_ja.md) | -| LLM-based ASR や中英日 + 中国語方言・地域アクセントの評価 | Fun-ASR-Nano | LLM-based モデル。decoder throughput が重要なら vLLM を使います。 | [vLLM guide](./vllm_guide.md) | +| LLM-based ASR や中英日 + 中国語方言・地域アクセントの評価 | Fun-ASR-Nano | Python で評価してから、checkpoint と interface に合わせて vLLM の経路を選びます。 | [vLLM の経路](#vllm-checkpoint-paths) | | オフライン長時間 ASR と匿名話者ラベル | MOSS-Transcribe-Diarize | 1 回のオフライン request で文字起こし、timestamps、録音内の匿名話者ラベルを返します。既知人物の識別ではなく、外部 VAD / speaker model も不要です。 | [MOSS deployment guide](./moss_transcribe_diarize.md) | | ライブ字幕やコールセンターストリーム | Runtime WebSocket service | 長時間接続、部分結果、エンドポイント検出に向いたランタイム。 | [Runtime service docs](../runtime/readme.md) | | Whisper / cloud ASR からの移行 | SenseVoice-Small で baseline を作り、必要に応じて比較 | まず強い標準ルートで評価してから、用途別に詰めるのが安全です。 | [Migration guide](./migration_from_whisper.md) | @@ -42,6 +42,12 @@ result = model.generate(input="meeting.wav") - **`paraformer`**: `paraformer-zh` に VAD と句読点復元を組み合わせた中国語向けの経路です。 - **`paraformer-en`**: `paraformer-en` と VAD を使う、OpenAI-style client 向けの英語文字起こしです。 - **`fun-asr-nano`**: `FunAudioLLM/Fun-ASR-Nano-2512` による中英日・中国語方言/地域アクセントの評価経路です。vLLM acceleration を試す場合は互換性のある runtime を選んでください。 +- **`moss-transcribe-diarize`**: 第三者の `OpenMOSS-Team/MOSS-Transcribe-Diarize` によるオフライン文字起こしと録音内の匿名話者ラベルです。[MOSS guide(英語)](./moss_transcribe_diarize.md)で専用の依存関係と remote code を確認し、構造化 segment には `verbose_json` を指定します。外部 VAD / speaker model は不要で、既知人物の識別ではありません。 + +ここでの alias は `AutoModel` を読み込む[サンプル server](../examples/openai_api/server.py)のものです。 +native vLLM や `AutoModelVLLM` を自動選択する設定ではありません。 +パッケージの `funasr-server` は別の loader / backend 選択を持つため、 +サービス間で alias や性能結果をそのまま流用しないでください。 この HTTP サンプルはトップレベルの `text` と `verbose_json` の各 segment の `text` を整形するため、形式を変えても感情/イベントタグは復元されません。 @@ -57,6 +63,21 @@ python examples/openai_api/smoke_test.py --base-url http://localhost:8000 --mode SDK、JavaScript、workflow、Postman、OpenAPI、Docker、Kubernetes は [OpenAI API example](../examples/openai_api/README_ja.md) から始めてください。 + + +## vLLM の checkpoint と interface + +| 経路 | checkpoint と interface | 次の資料 | +| --- | --- | --- | +| FunASR split-engine | 基本の `FunAudioLLM/Fun-ASR-Nano-2512` を `AutoModelVLLM` で読み込み、音声側は FunASR、decoder は vLLM が処理します。 | [Split-engine(英語)](./vllm_guide.md) | +| 公式 native vLLM | 変換済み `FunAudioLLM/Fun-ASR-Nano-2512-vllm` を vLLM の native 実装で読み込み、`/v1/audio/transcriptions` を利用します。`AutoModelVLLM` ではありません。 | [公式機能検証(英語)](./vllm_official_native_validation.md) | +| 過去の community native vLLM | `allendou/Fun-ASR-Nano-2512-vllm`、2026-08-13 の検証です。時間計測は当時の checkpoint と環境に限定されます。 | [過去の community 記録](./vllm_native_funasr_validation.md) | + +公式記録は固定 revision と既存環境での機能検証であり、新規インストール手順、 +持続負荷の benchmark、`/v1/realtime` の streaming 検証ではありません。 +過去の community 計測を公式モデルに付け替えないでください。 +MOSS は専用ガイドに従ってください。Nano の checkpoint と検証は MOSS の互換性を証明しません。 + ## ベンチマークしてから決める きれいな demo 音声 1 つだけでモデルを決めないでください。まず小さな代表セットで確認します。 diff --git a/docs/model_selection_ko.md b/docs/model_selection_ko.md index 775f86a35..6d11d9a10 100644 --- a/docs/model_selection_ko.md +++ b/docs/model_selection_ko.md @@ -29,7 +29,7 @@ result = model.generate(input="meeting.wav") | 빠른 다국어 프라이빗 전사 | SenseVoice-Small | ASR, 감정 태그, 음성 이벤트 태그, CPU/GPU 사용성이 균형 잡힌 기본 경로입니다. | [README quick start](../README_ko.md#빠른-시작) | | 중국어 중심 프로덕션 ASR | Paraformer-Large | VAD와 문장부호 복원을 함께 쓰는 성숙한 중국어 ASR 경로입니다. | [Tutorial](./tutorial/README.md) | | OpenAI API 예제의 영어 경로 | `paraformer-en` alias | OpenAI-style client에서 호환성을 확인하기 쉬운 가벼운 영어 경로입니다. | [OpenAI API example](../examples/openai_api/README_ko.md) | -| LLM-based ASR 또는 중영일 + 중국어 방언/지역 억양 평가 | Fun-ASR-Nano | LLM-based 모델입니다. decoder throughput이 중요하면 vLLM을 사용합니다. | [vLLM guide](./vllm_guide.md) | +| LLM-based ASR 또는 중영일 + 중국어 방언/지역 억양 평가 | Fun-ASR-Nano | Python에서 평가한 뒤 checkpoint와 interface에 맞는 vLLM 경로를 선택합니다. | [vLLM 경로 선택](#vllm-checkpoint-paths) | | 오프라인 장시간 ASR 및 익명 화자 라벨 | MOSS-Transcribe-Diarize | 한 번의 오프라인 request로 전사, timestamps, 녹음 내 익명 화자 라벨을 반환합니다. 알려진 인물을 식별하지 않으며 외부 VAD / speaker model도 필요하지 않습니다. | [MOSS deployment guide](./moss_transcribe_diarize.md) | | 라이브 자막 또는 콜센터 스트림 | Runtime WebSocket service | 장시간 연결, 부분 결과, endpointing에 맞춘 런타임입니다. | [Runtime service docs](../runtime/readme.md) | | Whisper / cloud ASR에서 전환 | SenseVoice-Small로 baseline을 만들고 필요하면 비교 | 강한 기본 경로로 먼저 평가한 뒤 용도별로 조정하는 편이 안전합니다. | [Migration guide](./migration_from_whisper.md) | @@ -42,6 +42,12 @@ result = model.generate(input="meeting.wav") - **`paraformer`**: `paraformer-zh`에 VAD와 문장부호 복원을 결합한 중국어 경로입니다. - **`paraformer-en`**: `paraformer-en`과 VAD를 사용하는 OpenAI-style client용 영어 전사입니다. - **`fun-asr-nano`**: `FunAudioLLM/Fun-ASR-Nano-2512`로 중영일·중국어 방언/지역 억양을 평가합니다. vLLM acceleration을 시험할 때는 호환되는 runtime을 선택하세요. +- **`moss-transcribe-diarize`**: 서드파티 `OpenMOSS-Team/MOSS-Transcribe-Diarize`의 오프라인 전사와 녹음 내 익명 화자 라벨입니다. [MOSS guide(영문)](./moss_transcribe_diarize.md)에서 전용 의존성과 remote code를 검토하고, 구조화된 segment에는 `verbose_json`을 요청하세요. 외부 VAD / speaker model이 필요 없으며 알려진 인물을 식별하지 않습니다. + +여기서 설명하는 alias는 `AutoModel`을 로드하는 [예제 server](../examples/openai_api/server.py)의 설정입니다. +native vLLM이나 `AutoModelVLLM`을 자동으로 선택하지 않습니다. +패키지의 `funasr-server`는 별도 loader / backend 선택 로직을 사용하므로, +서비스 사이에서 alias나 성능 결과를 그대로 재사용하지 마세요. 이 HTTP 예제는 최상위 `text`와 `verbose_json`의 각 segment `text`를 정리하므로, 형식을 바꿔도 감정/이벤트 태그가 복원되지 않습니다. 원래 태그가 필요하면 Python SDK를 @@ -57,6 +63,21 @@ python examples/openai_api/smoke_test.py --base-url http://localhost:8000 --mode SDK, JavaScript, workflow, Postman, OpenAPI, Docker, Kubernetes는 [OpenAI API example](../examples/openai_api/README_ko.md)에서 시작하세요. + + +## vLLM checkpoint와 interface 선택 + +| 경로 | checkpoint와 interface | 다음 문서 | +| --- | --- | --- | +| FunASR split-engine | 기본 `FunAudioLLM/Fun-ASR-Nano-2512`를 `AutoModelVLLM`으로 로드합니다. FunASR은 오디오 부분, vLLM은 decoder를 처리합니다. | [Split-engine(영문)](./vllm_guide.md) | +| 공식 native vLLM | 변환된 `FunAudioLLM/Fun-ASR-Nano-2512-vllm`을 vLLM의 native 구현으로 로드하고 `/v1/audio/transcriptions`를 사용합니다. `AutoModelVLLM` 로드가 아닙니다. | [공식 기능 검증(영문)](./vllm_official_native_validation.md) | +| 과거 community native vLLM | `allendou/Fun-ASR-Nano-2512-vllm`, 2026-08-13 검증입니다. 측정 시간은 당시 checkpoint와 환경에만 해당합니다. | [과거 community 기록](./vllm_native_funasr_validation.md) | + +공식 기록은 고정 revision과 기존 환경에서의 기능 검증이며, 신규 설치 절차, +지속 부하 benchmark 또는 `/v1/realtime` streaming 검증이 아닙니다. +과거 community 측정 시간을 공식 모델의 결과로 사용하지 마세요. +MOSS는 별도 가이드를 따르세요. Nano checkpoint와 검증은 MOSS 호환성을 입증하지 않습니다. + ## 벤치마크 후 결정하기 깨끗한 demo 오디오 하나만 보고 모델을 정하지 마세요. 먼저 작은 대표 세트로 확인합니다. diff --git a/docs/model_selection_zh.md b/docs/model_selection_zh.md index 09f913f9c..3e762c7d3 100644 --- a/docs/model_selection_zh.md +++ b/docs/model_selection_zh.md @@ -40,7 +40,7 @@ SenseVoice 生成转写和富文本标签;`fsmn-vad` 定位语音,`cam++` | 快速多语种私有转写 | SenseVoice-Small | 兼顾 ASR、情感标签、音频事件标签和 CPU 可用性。 | [README 快速开始](../README_zh.md#快速开始) | | 中文生产 ASR | Paraformer-Large | 成熟中文 ASR 路径,可组合 VAD 和标点。 | [教程](./tutorial/README_zh.md) | | OpenAI API 示例中的英文路由 | `paraformer-en` alias | 适合在 OpenAI 风格客户端里验证较轻量英文路径。 | [OpenAI API 示例](../examples/openai_api/README_zh.md) | -| LLM-based ASR 或中文/英文/日语 + 方言实验 | Fun-ASR-Nano | LLM-based 模型路径;解码吞吐敏感时配合 vLLM。 | [vLLM 指南](./vllm_guide.md) | +| LLM-based ASR 或中文/英文/日语 + 方言实验 | Fun-ASR-Nano | 先评估 Python 路径;split-engine 与原生 vLLM 的加载契约不同。 | [选择 vLLM 路径](#vllm-checkpoint-paths) | | 离线长音频 ASR 与匿名说话人标签 | MOSS-Transcribe-Diarize | 一次离线请求返回转写、时间戳和录音内匿名说话人标签;不识别已知人物,也不需要外部 VAD 或说话人模型。 | [MOSS 部署指南](./moss_transcribe_diarize_zh.md) | | 实时字幕或客服流式音频 | Runtime WebSocket 服务 | 面向长连接流式会话和中间结果。 | [Runtime 服务文档](../runtime/readme_cn.md) | | 录音归档批处理 | SenseVoice-Small 或 Paraformer-Large | 稳定离线转写路径;调用方负责 manifest、重试和日志。 | [批处理示例](../examples/batch_asr_improved.py) | @@ -54,6 +54,12 @@ SenseVoice 生成转写和富文本标签;`fsmn-vad` 定位语音,`cam++` - **`paraformer`** 使用 `paraformer-zh`,组合 VAD 和标点,适合评估中文转写。 - **`paraformer-en`** 使用 `paraformer-en`,组合 VAD,提供 OpenAI 风格客户端的英文转写路径。 - **`fun-asr-nano`** 使用 `FunAudioLLM/Fun-ASR-Nano-2512`,评估中文、英文、日语与中文方言/口音覆盖;测试 vLLM 加速时须选择兼容的运行路径。 +- **`moss-transcribe-diarize`** 使用第三方 `OpenMOSS-Team/MOSS-Transcribe-Diarize`,用于离线转写和录音内匿名说话人标签。先按 [MOSS 指南](./moss_transcribe_diarize_zh.md) 准备独立依赖并审查远程代码;需要结构化分段时请求 `verbose_json`。它不需要外部 VAD/说话人模型,也不识别已知人物。 + +这些别名属于加载 `AutoModel` 的[示例服务](../examples/openai_api/server.py), +不会配置原生 vLLM,也不会自动选择 `AutoModelVLLM`。 +包内 `funasr-server` 有独立的加载与后端选择逻辑;不要跨服务直接套用别名或性能结果, +先核对对应的 [HTTP 指南](../examples/openai_api/README_zh.md)。 示例 HTTP 服务会清理顶层 `text` 和 `verbose_json` 中各分段的 `text`; 切换到该格式不会恢复情感/事件标签。需要原始标签时,请使用 Python SDK, @@ -79,10 +85,25 @@ SDK、JavaScript、工作流、Postman、OpenAPI、Docker 和 Kubernetes 路径 | 可复现本地容器 demo | Docker Compose API | CPU-first smoke test;使用 CUDA 前先适配镜像。 | | 集群内私有服务 | Kubernetes API 模板 | 私有 `ClusterIP`、持久化模型缓存、`/health` probes 和 port-forward smoke test。 | | 实时音频 | Runtime WebSocket 服务 | 用真实音频验证 chunk size、VAD、断句、重连和客户端背压。 | -| LLM-based ASR 吞吐 | Fun-ASR-Nano 的 vLLM 路径 | vLLM 加速自回归解码;不适用于非自回归 Paraformer。 | +| LLM-based ASR 吞吐 | 在下方选择 split-engine 或原生 vLLM | 同时匹配 checkpoint、加载接口和已测环境;这不是 Paraformer 后端。 | 选择部署方式时可以参考 [部署选型表](./deployment_matrix_zh.md)。 + + +## 选择 vLLM 权重与接口 + +| 路径 | 权重与接口 | 下一步 | +| --- | --- | --- | +| FunASR split-engine | 基础 `FunAudioLLM/Fun-ASR-Nano-2512` 资产,由 `AutoModelVLLM` 加载;FunASR 处理音频部分,vLLM 处理解码器。 | [Split-engine 准备与边界](./vllm_guide_zh.md) | +| 官方原生 vLLM | 转换后的 `FunAudioLLM/Fun-ASR-Nano-2512-vllm` 快照,通过 vLLM 原生模型实现和 `/v1/audio/transcriptions` 提供服务;不是 `AutoModelVLLM` 加载。 | [官方功能验证](./vllm_official_native_validation_zh.md) | +| 历史社区原生 vLLM | 社区 `allendou/Fun-ASR-Nano-2512-vllm`,测试日期 2026-08-13;耗时只属于当时的权重与环境。 | [历史社区记录](./vllm_native_funasr_validation.md) | + +官方记录固定了模型 revision 和既有环境,不是全新安装配方、持续负载性能评测, +也不是 `/v1/realtime` 流式验证。不要把社区历史耗时用于官方模型。 +MOSS 请遵循其独立部署指南:以上 Nano 权重和验证不能证明 MOSS 的运行时兼容性。 +先一起确定模型、权重、接口与环境,再评估自己的工作负载。 + ## 上线前先 benchmark 不要只用一个干净 demo 文件选型。先准备一个小而有代表性的集合: diff --git a/tests/test_vllm_model_source_docs.py b/tests/test_vllm_model_source_docs.py index 777bafb47..12a349d56 100644 --- a/tests/test_vllm_model_source_docs.py +++ b/tests/test_vllm_model_source_docs.py @@ -1,6 +1,9 @@ from pathlib import Path +import ast +import importlib.util import pytest +from bs4 import BeautifulSoup ROOT = Path(__file__).resolve().parents[1] @@ -10,6 +13,65 @@ ] +@pytest.mark.parametrize("suffix", ["", "_zh", "_ja", "_ko"]) +def test_model_selection_distinguishes_checkpoint_and_service_paths(suffix): + text = (ROOT / f"docs/model_selection{suffix}.md").read_text() + language_suffix = "_zh" if suffix == "_zh" else "" + for target in (f"vllm_guide{language_suffix}.md", + f"vllm_official_native_validation{language_suffix}.md", + "vllm_native_funasr_validation.md"): + assert f"](./{target})" in text + assert (ROOT / "docs" / target).is_file() + for marker in ("AutoModelVLLM", "FunAudioLLM/Fun-ASR-Nano-2512-vllm", + "/v1/audio/transcriptions", "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/v1/realtime", "2026-08-13"): + assert marker in text + if suffix == "_zh": + assert "](./vllm_guide.md)" not in text + + +@pytest.mark.parametrize("suffix", ["", "_zh", "_ja", "_ko"]) +def test_model_selection_moss_alias_matches_real_service_configuration(suffix): + tree = ast.parse((ROOT / "examples/openai_api/server.py").read_text()) + config = next(node.value for node in ast.walk(tree) if isinstance(node, ast.Assign) + and any(isinstance(t, ast.Name) and t.id == "MODEL_CONFIGS" for t in node.targets)) + moss = next(value for key, value in zip(config.keys, config.values) + if isinstance(key, ast.Constant) and key.value == "moss-transcribe-diarize") + model = next(ast.literal_eval(value) for key, value in zip(moss.keys, moss.values) + if ast.literal_eval(key) == "model") + assert model == "OpenMOSS-Team/MOSS-Transcribe-Diarize" + text = (ROOT / f"docs/model_selection{suffix}.md").read_text() + assert "**`moss-transcribe-diarize`**" in text + assert f"`{model}`" in text + assert "verbose_json" in text + + +@pytest.mark.parametrize("language", ["en", "zh"]) +def test_model_selection_renders_distinct_local_native_and_split_links(language): + spec = importlib.util.spec_from_file_location( + "model_choice_documentation", ROOT / "web-pages/product-site/documentation.py") + documentation = importlib.util.module_from_spec(spec) + spec.loader.exec_module(documentation) + catalogue = documentation.load_catalogue() + entry = next(page for page in catalogue["pages"] if page["slug"] == "model-selection") + rendered = documentation.render_source(entry, language, catalogue) + soup = BeautifulSoup(rendered["content_html"], "html.parser") + prefix = "/en" if language == "en" else "" + targets = {a.get("href") for a in soup.select("a[href]")} + for slug in ("vllm", "official-native-vllm", "native-vllm", "moss-transcribe-diarize"): + assert f"{prefix}/docs/{slug}.html" in targets + ids = [node["id"] for node in soup.select("[id]")] + assert ids.count("vllm-checkpoint-paths") == 1 + assert len(ids) == len(set(ids)), "Existing and new section anchors must stay unique" + + +def test_documentation_hub_keeps_official_and_historical_native_entries_separate(): + text = (ROOT / "docs/README.md").read_text() + for target in ("vllm_official_native_validation.md", "vllm_official_native_validation_zh.md", + "vllm_native_funasr_validation.md"): + assert f"]({target})" in text + assert "2026-08-13" in text + + @pytest.mark.parametrize("relpath", VLLM_GUIDES) def test_vllm_guides_distinguish_official_and_native_model_paths(relpath): text = (ROOT / relpath).read_text(encoding="utf-8") diff --git a/web-pages/product-site/tests/browser/model-selection-tags.spec.ts b/web-pages/product-site/tests/browser/model-selection-tags.spec.ts index 613a125f4..1c14afd59 100644 --- a/web-pages/product-site/tests/browser/model-selection-tags.spec.ts +++ b/web-pages/product-site/tests/browser/model-selection-tags.spec.ts @@ -1,5 +1,50 @@ import { expect, test } from '@playwright/test'; +for (const width of [320, 390, 1440]) { + for (const language of ['zh', 'en']) { + test(`Model choice preserves distinct runtime journeys: ${language} ${width}px`, async ({ page }, testInfo) => { + const prefix = language === 'en' ? '/en' : ''; + const errors: string[] = []; + page.on('pageerror', error => errors.push(error.message)); + await page.setViewportSize({ width, height: 900 }); + await page.goto(`${prefix}/docs/model-selection.html`); + const anchor = page.locator('#vllm-checkpoint-paths'); + await expect(anchor).toHaveCount(1); + await page.locator('.docs-article a[href="#vllm-checkpoint-paths"]').click(); + await expect(page).toHaveURL(/#vllm-checkpoint-paths$/); + const heading = anchor.locator('xpath=following::h2[1]'); + await expect(heading).toBeInViewport({ ratio: 1 }); + await expect.poll(() => heading.evaluate(node => { + const box = node.getBoundingClientRect(); + const painted = document.elementFromPoint(box.left + Math.min(20, box.width / 2), box.top + box.height / 2); + return painted?.closest('h2') === node; + })).toBe(true); + const table = page.locator('.docs-article table').filter({ hasText: 'allendou/Fun-ASR-Nano-2512-vllm' }); + await expect(table).toHaveCount(1); + await expect(table).toContainText('AutoModelVLLM'); + await expect(table).toContainText('/v1/audio/transcriptions'); + expect(await page.evaluate(() => document.documentElement.scrollWidth - innerWidth)).toBeLessThanOrEqual(1); + await page.evaluate(() => new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)))); + await page.screenshot({ path: testInfo.outputPath('runtime-choice.png') }); + for (const [slug, marker] of [ + ['official-native-vllm', 'a4362c943d48951f98ca2a62181cc028970270c5'], + ['vllm', 'AutoModelVLLM'], + ['native-vllm', 'allendou/Fun-ASR-Nano-2512-vllm'], + ]) { + await table.locator(`a[href="${prefix}/docs/${slug}.html"]`).click(); + await expect(page).toHaveURL(new RegExp(`${prefix}/docs/${slug}.html$`)); + await expect(page.locator('.docs-article')).toContainText(marker); + expect(await page.evaluate(() => document.documentElement.scrollWidth - innerWidth)).toBeLessThanOrEqual(1); + await page.goBack(); + } + await page.locator(`.docs-article a[href="${prefix}/docs/moss-transcribe-diarize.html"]`).first().click(); + await expect(page).toHaveURL(new RegExp(`${prefix}/docs/moss-transcribe-diarize.html$`)); + await expect(page.locator('.docs-article')).toContainText('OpenMOSS'); + expect(errors).toEqual([]); + }); + } +} + for (const width of [390, 1440]) { for (const language of ['zh', 'en']) { test(`Model selection to raw tag recipe: ${language} ${width}px`, async ({ page }, testInfo) => { From 53fbe89b652ebf2d0100fb5b9d1d419bceb68d4e Mon Sep 17 00:00:00 2001 From: zhifu gao Date: Tue, 8 Sep 2026 18:41:03 +0000 Subject: [PATCH 2/2] test: keep rendering checks in the product-site suite Signed-off-by: zhifu gao --- tests/test_vllm_model_source_docs.py | 21 ------------------- .../product-site/tests/test_documentation.py | 12 +++++++++++ 2 files changed, 12 insertions(+), 21 deletions(-) diff --git a/tests/test_vllm_model_source_docs.py b/tests/test_vllm_model_source_docs.py index 12a349d56..ff54c0fe6 100644 --- a/tests/test_vllm_model_source_docs.py +++ b/tests/test_vllm_model_source_docs.py @@ -1,9 +1,7 @@ from pathlib import Path import ast -import importlib.util import pytest -from bs4 import BeautifulSoup ROOT = Path(__file__).resolve().parents[1] @@ -45,25 +43,6 @@ def test_model_selection_moss_alias_matches_real_service_configuration(suffix): assert "verbose_json" in text -@pytest.mark.parametrize("language", ["en", "zh"]) -def test_model_selection_renders_distinct_local_native_and_split_links(language): - spec = importlib.util.spec_from_file_location( - "model_choice_documentation", ROOT / "web-pages/product-site/documentation.py") - documentation = importlib.util.module_from_spec(spec) - spec.loader.exec_module(documentation) - catalogue = documentation.load_catalogue() - entry = next(page for page in catalogue["pages"] if page["slug"] == "model-selection") - rendered = documentation.render_source(entry, language, catalogue) - soup = BeautifulSoup(rendered["content_html"], "html.parser") - prefix = "/en" if language == "en" else "" - targets = {a.get("href") for a in soup.select("a[href]")} - for slug in ("vllm", "official-native-vllm", "native-vllm", "moss-transcribe-diarize"): - assert f"{prefix}/docs/{slug}.html" in targets - ids = [node["id"] for node in soup.select("[id]")] - assert ids.count("vllm-checkpoint-paths") == 1 - assert len(ids) == len(set(ids)), "Existing and new section anchors must stay unique" - - def test_documentation_hub_keeps_official_and_historical_native_entries_separate(): text = (ROOT / "docs/README.md").read_text() for target in ("vllm_official_native_validation.md", "vllm_official_native_validation_zh.md", diff --git a/web-pages/product-site/tests/test_documentation.py b/web-pages/product-site/tests/test_documentation.py index 7f4dc8b11..b165b6746 100644 --- a/web-pages/product-site/tests/test_documentation.py +++ b/web-pages/product-site/tests/test_documentation.py @@ -140,6 +140,18 @@ def test_documentation_links_are_mapped_to_local_pages_or_repository(output): assert href.startswith(('/', '#', 'https://', 'http://', 'mailto:')), href +@pytest.mark.parametrize('prefix', ['', 'en/']) +def test_model_selection_renders_distinct_local_native_and_split_links(output, prefix): + page = BeautifulSoup((output / prefix / 'docs/model-selection.html').read_text(), 'html.parser') + article = page.select_one('.docs-article') + targets = {a['href'] for a in article.select('a[href]')} + for slug in ('vllm', 'official-native-vllm', 'native-vllm', 'moss-transcribe-diarize'): + assert f'/{prefix}docs/{slug}.html' in targets + ids = [node['id'] for node in page.select('[id]')] + assert ids.count('vllm-checkpoint-paths') == 1 + assert len(ids) == len(set(ids)), 'Existing and new section anchors must stay unique' + + def test_source_fragment_links_keep_unicode_and_punctuation_boundaries(output): for prefix in ('', 'en/'): for path in (output / prefix / 'docs').glob('*.html'):