From e7ddad713ed056e553e920c1c4a33adf05dc5bb6 Mon Sep 17 00:00:00 2001 From: LauraGPT <18321252+LauraGPT@users.noreply.github.com> Date: Tue, 1 Sep 2026 03:02:33 +0000 Subject: [PATCH] feat: serve MOSS diarization across deployment stack Signed-off-by: LauraGPT <18321252+LauraGPT@users.noreply.github.com> --- .github/workflows/test-moss-adapter.yml | 21 ++++- README.md | 4 + README_ja.md | 4 + README_ko.md | 4 + README_zh.md | 4 + docs/index.rst | 2 + docs/moss_transcribe_diarize.md | 38 ++++++++ docs/moss_transcribe_diarize_zh.md | 35 ++++++++ examples/openai_api/CLIENTS.md | 4 + examples/openai_api/Dockerfile.moss | 27 ++++++ examples/openai_api/GRADIO.md | 4 + examples/openai_api/GRADIO_zh.md | 4 + examples/openai_api/JAVASCRIPT.md | 5 ++ examples/openai_api/JAVASCRIPT_zh.md | 5 ++ examples/openai_api/OPENAPI.md | 6 +- examples/openai_api/OPENAPI_zh.md | 6 +- examples/openai_api/README.md | 6 ++ examples/openai_api/README_ja.md | 5 ++ examples/openai_api/README_ko.md | 5 ++ examples/openai_api/README_zh.md | 5 ++ examples/openai_api/WORKFLOWS.md | 5 ++ examples/openai_api/WORKFLOWS_zh.md | 4 + examples/openai_api/docker-compose.moss.yml | 26 ++++++ examples/openai_api/kubernetes/README.md | 5 ++ examples/openai_api/kubernetes/README_zh.md | 5 ++ .../kubernetes/funasr-moss-api.yaml | 86 +++++++++++++++++++ examples/openai_api/openapi.json | 3 +- examples/openai_api/server.py | 10 ++- funasr/bin/_server_app.py | 16 +++- funasr/bin/server.py | 11 ++- tests/test_moss_transcribe_diarize_docs.py | 80 +++++++++++++++++ tests/test_server_app_openai_segments.py | 47 ++++++++++ web-pages/product-site/data/deployments.json | 29 ++++++- web-pages/product-site/tests/test_output.py | 10 ++- web-pages/product-site/tests/test_registry.py | 27 ++++-- 35 files changed, 535 insertions(+), 23 deletions(-) create mode 100644 examples/openai_api/Dockerfile.moss create mode 100644 examples/openai_api/docker-compose.moss.yml create mode 100644 examples/openai_api/kubernetes/funasr-moss-api.yaml diff --git a/.github/workflows/test-moss-adapter.yml b/.github/workflows/test-moss-adapter.yml index 53a97c82e..25c978de9 100644 --- a/.github/workflows/test-moss-adapter.yml +++ b/.github/workflows/test-moss-adapter.yml @@ -5,7 +5,13 @@ on: paths: - "funasr/models/moss_transcribe_diarize/**" - "funasr/auto/auto_model.py" + - "funasr/bin/_server_app.py" + - "funasr/bin/server.py" + - "examples/openai_api/**" + - "README*.md" + - "docs/index.rst" - "docs/moss_transcribe_diarize*.md" + - "web-pages/product-site/data/deployments.json" - "tests/test_moss_transcribe_diarize_*.py" - ".github/workflows/test-moss-adapter.yml" push: @@ -14,7 +20,13 @@ on: paths: - "funasr/models/moss_transcribe_diarize/**" - "funasr/auto/auto_model.py" + - "funasr/bin/_server_app.py" + - "funasr/bin/server.py" + - "examples/openai_api/**" + - "README*.md" + - "docs/index.rst" - "docs/moss_transcribe_diarize*.md" + - "web-pages/product-site/data/deployments.json" - "tests/test_moss_transcribe_diarize_*.py" - ".github/workflows/test-moss-adapter.yml" @@ -45,9 +57,14 @@ jobs: funasr/models/moss_transcribe_diarize/model.py \ tests/test_moss_transcribe_diarize_model.py \ tests/test_moss_transcribe_diarize_docs.py - python -m py_compile funasr/models/moss_transcribe_diarize/model.py + python -m py_compile \ + funasr/models/moss_transcribe_diarize/model.py \ + funasr/bin/_server_app.py \ + funasr/bin/server.py \ + examples/openai_api/server.py - name: Run adapter and documentation contracts run: | python -m pytest -q \ tests/test_moss_transcribe_diarize_model.py \ - tests/test_moss_transcribe_diarize_docs.py + tests/test_moss_transcribe_diarize_docs.py \ + tests/test_server_app_openai_segments.py diff --git a/README.md b/README.md index 4a2d6bbbc..e47d6de50 100644 --- a/README.md +++ b/README.md @@ -251,8 +251,12 @@ pip install torch torchaudio pip install funasr vllm fastapi uvicorn python-multipart funasr-server --device cuda # → POST /v1/audio/transcriptions at localhost:8000 +# Joint long-form ASR + anonymous speaker labels (offline HTTP): +funasr-server --model moss-transcribe-diarize --device cuda:0 ``` +[MOSS service, Docker, Kubernetes, vLLM, SGLang, LocalAI, and FunClip guide →](./docs/moss_transcribe_diarize.md) + Verify it with a public sample: ```bash diff --git a/README_ja.md b/README_ja.md index 36a2ae103..b64f5ff36 100644 --- a/README_ja.md +++ b/README_ja.md @@ -140,11 +140,15 @@ pip install funasr # OpenAI互換API(推奨) pip install funasr fastapi uvicorn python-multipart funasr-server --device cuda +# オフライン長時間音声 ASR + 匿名 speaker label: +funasr-server --model moss-transcribe-diarize --device cuda:0 # Dockerストリーミングサービス docker pull registry.cn-hangzhou.aliyuncs.com/funasr_repo/funasr:funasr-runtime-sdk-online-cpu-0.1.12 ``` +[MOSS service / Docker / Kubernetes / vLLM / SGLang / LocalAI / FunClip guide →](./docs/moss_transcribe_diarize.md) + CPU/エッジで Python なしのオフライン ASR が必要な場合は、llama.cpp / GGUF ランタイムを使えます:[funasr.com/deploy/llama-cpp](https://www.funasr.com/en/deploy/llama-cpp.html) · [Fun-ASR-Nano-GGUF](https://huggingface.co/FunAudioLLM/Fun-ASR-Nano-GGUF) · [SenseVoiceSmall-GGUF](https://huggingface.co/FunAudioLLM/SenseVoiceSmall-GGUF)。 [Colab quickstart →](./examples/colab/README_ja.md) · [OpenAI API example →](./examples/openai_api/README_ja.md) · [Client recipes →](./examples/openai_api/CLIENTS.md) · [Workflow recipes →](./examples/openai_api/WORKFLOWS.md) · [Postman collection →](./examples/openai_api/POSTMAN.md) · [OpenAPI spec →](./examples/openai_api/OPENAPI.md) · [Security guide →](./examples/openai_api/SECURITY.md) · [Deployment matrix →](./docs/deployment_matrix_ja.md) · [デプロイドキュメント →](./runtime/readme.md) · [Agent連携 →](https://modelscope.github.io/FunASR/agent.html) diff --git a/README_ko.md b/README_ko.md index 192142ef5..ea57f313e 100644 --- a/README_ko.md +++ b/README_ko.md @@ -140,11 +140,15 @@ pip install funasr # OpenAI 호환 API (권장) pip install funasr fastapi uvicorn python-multipart funasr-server --device cuda +# 오프라인 장시간 ASR + 익명 speaker label: +funasr-server --model moss-transcribe-diarize --device cuda:0 # Docker 스트리밍 서비스 docker pull registry.cn-hangzhou.aliyuncs.com/funasr_repo/funasr:funasr-runtime-sdk-online-cpu-0.1.12 ``` +[MOSS service / Docker / Kubernetes / vLLM / SGLang / LocalAI / FunClip guide →](./docs/moss_transcribe_diarize.md) + CPU/엣지에서 Python 없이 오프라인 ASR만 필요하다면 llama.cpp / GGUF 런타임을 사용할 수 있습니다: [funasr.com/deploy/llama-cpp](https://www.funasr.com/en/deploy/llama-cpp.html) · [Fun-ASR-Nano-GGUF](https://huggingface.co/FunAudioLLM/Fun-ASR-Nano-GGUF) · [SenseVoiceSmall-GGUF](https://huggingface.co/FunAudioLLM/SenseVoiceSmall-GGUF). [Colab quickstart →](./examples/colab/README_ko.md) · [OpenAI API example →](./examples/openai_api/README_ko.md) · [Client recipes →](./examples/openai_api/CLIENTS.md) · [Workflow recipes →](./examples/openai_api/WORKFLOWS.md) · [Postman collection →](./examples/openai_api/POSTMAN.md) · [OpenAPI spec →](./examples/openai_api/OPENAPI.md) · [Security guide →](./examples/openai_api/SECURITY.md) · [Deployment matrix →](./docs/deployment_matrix_ko.md) · [배포 문서 →](./runtime/readme.md) · [Agent 연동 →](https://modelscope.github.io/FunASR/agent.html) diff --git a/README_zh.md b/README_zh.md index cfbc545b7..d448559cd 100644 --- a/README_zh.md +++ b/README_zh.md @@ -275,8 +275,12 @@ funasr *.wav --output-format srt --output-dir ./output pip install funasr fastapi uvicorn python-multipart funasr-server --model sensevoice --device cuda # → POST /v1/audio/transcriptions,地址 localhost:8000 +# 离线长音频联合转写 + 匿名说话人标签: +funasr-server --model moss-transcribe-diarize --device cuda:0 ``` +[MOSS 服务、Docker、Kubernetes、vLLM、SGLang、LocalAI 与 FunClip 部署指南 →](./docs/moss_transcribe_diarize_zh.md) + 使用公开样例音频验证服务: ```bash diff --git a/docs/index.rst b/docs/index.rst index 3fc0b01ec..dd200430a 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -73,6 +73,8 @@ Overview ./deployment_matrix.md ./deployment_matrix_zh.md + ./moss_transcribe_diarize.md + ./moss_transcribe_diarize_zh.md ./runtime/readme.md ./runtime/docs/SDK_tutorial_online.md ./runtime/docs/SDK_tutorial.md diff --git a/docs/moss_transcribe_diarize.md b/docs/moss_transcribe_diarize.md index a7e348642..dd017252c 100644 --- a/docs/moss_transcribe_diarize.md +++ b/docs/moss_transcribe_diarize.md @@ -95,6 +95,44 @@ clients keep the exact tagged generation in `raw_text`; in structured mode, `raw_text` is the cleaned `text` returned by vLLM and the authoritative speaker metadata is in `sentence_info`. +## FunASR OpenAI-compatible service + +The built-in offline HTTP service exposes the same normalized result through +`/v1/audio/transcriptions`. It loads the pinned Transformers revision and does +not attach an external VAD or speaker model: + +```bash +python -m pip install "transformers>=5.6,<6" fastapi uvicorn python-multipart +funasr-server --model moss-transcribe-diarize --device cuda:0 --port 8000 + +curl -fsS http://127.0.0.1:8000/v1/audio/transcriptions \ + -F file=@meeting.wav \ + -F model=moss-transcribe-diarize \ + -F response_format=verbose_json +``` + +The response contains `text`, audio `duration`, and `segments` with `start`, +`end`, `text`, and anonymous `speaker` labels. The request does not need +`spk=true`; if a generic client sends it, the service still uses MOSS's native +labels and does not start a second diarization pipeline. + +For a reproducible GPU container, build from the repository root with +`examples/openai_api/docker-compose.moss.yml`. Kubernetes operators can build +the same `funasr-moss-api:local` image and apply +`examples/openai_api/kubernetes/funasr-moss-api.yaml`; replace the local image +reference with the immutable digest from their registry before rollout. + +MOSS is an offline long-form model, so it is not exposed by FunASR's realtime +WebSocket service. Use the HTTP endpoint for complete files. FunClip consumes +the same `sentence_info` contract for speaker-aware subtitles and clips. + +The built-in service path was reproduced on one H100 80GB with Transformers +`5.16.0.dev0` and Torch `2.11.0+cu130`. The pinned model processed the bundled +6.000-second sample (`ea03e1f473ad1618a03da3327a545369cb8f6f06cb0f4115535e5a866167d47e`) +through the real HTTP endpoint and returned one non-empty monotonic segment +labelled `S01`, with `duration=6.0`. This is a functional service-contract +smoke, not an accuracy, throughput, concurrency, or production-capacity claim. + ## Choose a serving path | Path | Environment | Response contract | Use when | diff --git a/docs/moss_transcribe_diarize_zh.md b/docs/moss_transcribe_diarize_zh.md index 6f5803800..338fd2703 100644 --- a/docs/moss_transcribe_diarize_zh.md +++ b/docs/moss_transcribe_diarize_zh.md @@ -85,6 +85,41 @@ speaker `segments` 直接映射到 `sentence_info`。认证可通过 `vllm_api_k 此时 `raw_text` 保留原始标签生成;结构化模式下,`raw_text` 是 vLLM 返回的清理后 `text`,权威说话人信息位于 `sentence_info`。 +## FunASR OpenAI 兼容服务 + +内置离线 HTTP 服务通过 `/v1/audio/transcriptions` 返回同一套标准化结果。 +服务加载固定的 Transformers revision,不会外挂 VAD 或第二套说话人模型: + +```bash +python -m pip install "transformers>=5.6,<6" fastapi uvicorn python-multipart +funasr-server --model moss-transcribe-diarize --device cuda:0 --port 8000 + +curl -fsS http://127.0.0.1:8000/v1/audio/transcriptions \ + -F file=@meeting.wav \ + -F model=moss-transcribe-diarize \ + -F response_format=verbose_json +``` + +响应包含 `text`、音频 `duration`,以及带 `start`、`end`、`text` 和匿名 +`speaker` 标签的 `segments`。请求不需要设置 `spk=true`;即使通用客户端传入 +该字段,服务也只使用 MOSS 原生标签,不会启动第二套说话人流水线。 + +GPU 容器可在仓库根目录使用 +`examples/openai_api/docker-compose.moss.yml` 构建。Kubernetes 运维可构建同一 +`funasr-moss-api:local` 镜像,再应用 +`examples/openai_api/kubernetes/funasr-moss-api.yaml`;上线前应将本地镜像名 +替换为内部镜像仓库的不可变 digest。 + +MOSS 是离线长音频模型,不接入 FunASR 的实时 WebSocket 服务;完整文件请使用 +HTTP 接口。FunClip 通过同一套 `sentence_info` 契约生成带说话人信息的字幕与 +片段。 + +内置服务路径已在一张 H100 80GB、Transformers `5.16.0.dev0`、Torch +`2.11.0+cu130` 环境复现。固定 revision 的模型通过真实 HTTP 接口处理仓库自带 +6.000 秒样例(`ea03e1f473ad1618a03da3327a545369cb8f6f06cb0f4115535e5a866167d47e`), +返回一个非空、时间单调且标记为 `S01` 的 segment,`duration=6.0`。这只是服务 +契约 smoke test,不代表准确率、吞吐、并发或生产容量。 + ## 选择服务后端 | 路径 | 环境 | 响应契约 | 适用场景 | diff --git a/examples/openai_api/CLIENTS.md b/examples/openai_api/CLIENTS.md index 9e405c418..c73bc3766 100644 --- a/examples/openai_api/CLIENTS.md +++ b/examples/openai_api/CLIENTS.md @@ -20,6 +20,10 @@ If the server is on another machine, replace `localhost` with the reachable host | `paraformer` | Mandarin production transcription | Includes VAD and punctuation. | | `paraformer-en` | English transcription | Smaller English-only route. | | `fun-asr-nano` | LLM-based ASR experiments | Pair with vLLM for higher throughput deployments. | +| `moss-transcribe-diarize` | Offline multi-speaker long-form audio | Returns timestamps and anonymous speaker labels in `verbose_json`; do not add external VAD or diarization. | + +For MOSS server, Docker, Kubernetes, vLLM, SGLang Omni, LocalAI, and FunClip +paths, use the [dedicated deployment guide](../../docs/moss_transcribe_diarize.md). ## Python OpenAI SDK diff --git a/examples/openai_api/Dockerfile.moss b/examples/openai_api/Dockerfile.moss new file mode 100644 index 000000000..490bbd789 --- /dev/null +++ b/examples/openai_api/Dockerfile.moss @@ -0,0 +1,27 @@ +FROM pytorch/pytorch:2.9.1-cuda12.8-cudnn9-runtime + +ENV PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 \ + PIP_NO_CACHE_DIR=1 \ + FUNASR_MODEL=moss-transcribe-diarize \ + FUNASR_DEVICE=cuda:0 \ + FUNASR_PORT=8000 + +WORKDIR /opt/funasr + +RUN apt-get update \ + && apt-get install -y --no-install-recommends ffmpeg git libsndfile1 \ + && rm -rf /var/lib/apt/lists/* + +COPY . /opt/funasr + +RUN python -m pip install --upgrade pip \ + && python -m pip install . fastapi "uvicorn[standard]" python-multipart \ + && python -m pip install "transformers>=5.6,<6" + +EXPOSE 8000 + +HEALTHCHECK --interval=30s --timeout=5s --start-period=10m --retries=3 \ + CMD python -c "import os, urllib.request; urllib.request.urlopen('http://127.0.0.1:%s/health' % os.getenv('FUNASR_PORT', '8000'), timeout=3).read()" || exit 1 + +CMD ["sh", "-c", "funasr-server --host 0.0.0.0 --port ${FUNASR_PORT:-8000} --device ${FUNASR_DEVICE:-cuda:0} --model ${FUNASR_MODEL:-moss-transcribe-diarize}"] diff --git a/examples/openai_api/GRADIO.md b/examples/openai_api/GRADIO.md index d3c6cb14d..ea148f7d1 100644 --- a/examples/openai_api/GRADIO.md +++ b/examples/openai_api/GRADIO.md @@ -51,6 +51,10 @@ For OpenAI SDK clients, remember that SDK base URLs include `/v1`; this Gradio d | `paraformer` | Mandarin-oriented production transcription. | | `paraformer-en` | English-only compatibility checks. | | `fun-asr-nano` | LLM-based ASR and vLLM experiments. | +| `moss-transcribe-diarize` | Offline long-form transcription with timestamps and anonymous speaker labels; select `verbose_json`. | + +MOSS requires the dedicated GPU service environment and is not a realtime +microphone model. See the [MOSS deployment guide](../../docs/moss_transcribe_diarize.md). See the [model selection guide](../../docs/model_selection.md) for a deeper comparison. diff --git a/examples/openai_api/GRADIO_zh.md b/examples/openai_api/GRADIO_zh.md index 7250dade7..fa0cca77b 100644 --- a/examples/openai_api/GRADIO_zh.md +++ b/examples/openai_api/GRADIO_zh.md @@ -51,6 +51,10 @@ OpenAI SDK 的 base URL 需要包含 `/v1`;这个 Gradio demo 使用的是不 | `paraformer` | 中文生产转写。 | | `paraformer-en` | 英文兼容性检查。 | | `fun-asr-nano` | LLM-based ASR 和 vLLM 实验。 | +| `moss-transcribe-diarize` | 离线长音频转写、时间戳和匿名说话人标签;请选择 `verbose_json`。 | + +MOSS 需要专用 GPU 服务环境,不适合作为实时麦克风模型。详见 +[MOSS 部署指南](../../docs/moss_transcribe_diarize_zh.md)。 更完整的对比见 [模型选择指南](../../docs/model_selection_zh.md)。 diff --git a/examples/openai_api/JAVASCRIPT.md b/examples/openai_api/JAVASCRIPT.md index 95c98af78..82e3fe594 100644 --- a/examples/openai_api/JAVASCRIPT.md +++ b/examples/openai_api/JAVASCRIPT.md @@ -11,6 +11,11 @@ cd examples/openai_api python server.py --model sensevoice --device cuda --port 8000 ``` +For offline multi-speaker files, start the isolated MOSS service with +`funasr-server --model moss-transcribe-diarize --device cuda:0`, set +`FUNASR_MODEL=moss-transcribe-diarize`, and request `verbose_json`. See the +[MOSS deployment guide](../../docs/moss_transcribe_diarize.md). + Then verify the service from another terminal: ```bash diff --git a/examples/openai_api/JAVASCRIPT_zh.md b/examples/openai_api/JAVASCRIPT_zh.md index f85e1b78e..c91317239 100644 --- a/examples/openai_api/JAVASCRIPT_zh.md +++ b/examples/openai_api/JAVASCRIPT_zh.md @@ -11,6 +11,11 @@ cd examples/openai_api python server.py --model sensevoice --device cuda --port 8000 ``` +处理离线多人音频时,使用 +`funasr-server --model moss-transcribe-diarize --device cuda:0` 启动独立服务,设置 +`FUNASR_MODEL=moss-transcribe-diarize` 并请求 `verbose_json`。详见 +[MOSS 部署指南](../../docs/moss_transcribe_diarize_zh.md)。 + 在另一个终端验证服务: ```bash diff --git a/examples/openai_api/OPENAPI.md b/examples/openai_api/OPENAPI.md index 56b750612..a65036634 100644 --- a/examples/openai_api/OPENAPI.md +++ b/examples/openai_api/OPENAPI.md @@ -38,7 +38,7 @@ Replace them with the URL reachable from your app, container, or workflow runtim | Field | Type | Required | Notes | |---|---|---|---| | `file` | binary | yes | Audio file such as wav, mp3, flac, m4a, ogg, or webm. | -| `model` | string | no | Defaults to `sensevoice`; available aliases are listed by `/v1/models`. | +| `model` | string | no | Defaults to `sensevoice`; `/v1/models` also lists `moss-transcribe-diarize` for offline long-form transcription with native anonymous speaker labels. | | `language` | string | no | Optional language hint. | | `response_format` | string | no | Use `json` or `verbose_json`. | @@ -51,3 +51,7 @@ curl -fsS http://localhost:8000/openapi.json > /tmp/funasr-openapi-live.json ``` The live FastAPI schema may include framework-specific validation details; this checked-in spec keeps the public integration surface small and stable. + +MOSS requires its isolated Transformers environment. Start it with +`funasr-server --model moss-transcribe-diarize --device cuda:0` and request +`response_format=verbose_json`; see the [deployment guide](../../docs/moss_transcribe_diarize.md). diff --git a/examples/openai_api/OPENAPI_zh.md b/examples/openai_api/OPENAPI_zh.md index bdad4ba04..297295a61 100644 --- a/examples/openai_api/OPENAPI_zh.md +++ b/examples/openai_api/OPENAPI_zh.md @@ -38,7 +38,7 @@ | Field | Type | Required | 说明 | |---|---|---|---| | `file` | binary | yes | 音频文件,例如 wav、mp3、flac、m4a、ogg 或 webm。 | -| `model` | string | no | 默认 `sensevoice`;可用别名由 `/v1/models` 返回。 | +| `model` | string | no | 默认 `sensevoice`;`/v1/models` 也会列出用于离线长音频与原生匿名说话人标签的 `moss-transcribe-diarize`。 | | `language` | string | no | 可选语言提示。 | | `response_format` | string | no | 使用 `json` 或 `verbose_json`。 | @@ -51,3 +51,7 @@ curl -fsS http://localhost:8000/openapi.json > /tmp/funasr-openapi-live.json ``` 实时 FastAPI schema 可能包含框架级校验细节;仓库中的静态规范保留更小、更稳定的公开集成面。 + +MOSS 需要独立 Transformers 环境。使用 +`funasr-server --model moss-transcribe-diarize --device cuda:0` 启动,并请求 +`response_format=verbose_json`;详见 [部署指南](../../docs/moss_transcribe_diarize_zh.md)。 diff --git a/examples/openai_api/README.md b/examples/openai_api/README.md index bf334dc29..24a95a8d6 100644 --- a/examples/openai_api/README.md +++ b/examples/openai_api/README.md @@ -92,6 +92,12 @@ curl http://localhost:8000/v1/audio/transcriptions \ | `paraformer` | 120x realtime | 15x realtime | zh/en | Punctuation | | `paraformer-en` | 120x realtime | 15x realtime | en | English only | | `fun-asr-nano` | 17x realtime | 3.6x realtime | zh/en/ja + Chinese dialects/accents | LLM-based, timestamps | +| `moss-transcribe-diarize` | Validate on target GPU | Offline only | Multilingual | Long-form timestamps + anonymous speaker labels | + +MOSS uses a pinned third-party HF revision and must not be combined with an +external VAD or speaker model. See the [complete MOSS deployment guide](../../docs/moss_transcribe_diarize.md) +for `funasr-server`, Docker Compose, Kubernetes, vLLM, SGLang Omni, LocalAI, +and FunClip paths. ## API Endpoints diff --git a/examples/openai_api/README_ja.md b/examples/openai_api/README_ja.md index 9c4daa28d..4368c4939 100644 --- a/examples/openai_api/README_ja.md +++ b/examples/openai_api/README_ja.md @@ -89,6 +89,11 @@ curl http://localhost:8000/v1/audio/transcriptions \ | `paraformer` | 120x realtime | 15x realtime | zh/en | 句読点復元 | | `paraformer-en` | 120x realtime | 15x realtime | en | 英語認識 | | `fun-asr-nano` | 17x realtime | 3.6x realtime | 中英日 + 中国語方言・地域アクセント | LLM-based、タイムスタンプ | +| `moss-transcribe-diarize` | 対象 GPU で要検証 | オフラインのみ | 多言語 | 長時間 timestamp + 匿名 speaker label | + +MOSS は固定 revision の third-party HF model を使用し、外部 VAD / speaker model +とは併用しません。[MOSS deployment guide](../../docs/moss_transcribe_diarize.md) に +`funasr-server`、Docker、Kubernetes、vLLM、SGLang、LocalAI、FunClip をまとめています。 ## API エンドポイント diff --git a/examples/openai_api/README_ko.md b/examples/openai_api/README_ko.md index 95e0670a5..872d6bc0c 100644 --- a/examples/openai_api/README_ko.md +++ b/examples/openai_api/README_ko.md @@ -89,6 +89,11 @@ curl http://localhost:8000/v1/audio/transcriptions \ | `paraformer` | 120x realtime | 15x realtime | zh/en | 문장부호 복원 | | `paraformer-en` | 120x realtime | 15x realtime | en | 영어 인식 | | `fun-asr-nano` | 17x realtime | 3.6x realtime | 중영일 + 중국어 방언/지역 억양 | LLM-based, 타임스탬프 | +| `moss-transcribe-diarize` | 대상 GPU 검증 필요 | 오프라인 전용 | 다국어 | 장시간 timestamp + 익명 speaker label | + +MOSS는 고정 revision의 third-party HF model을 사용하며 외부 VAD / speaker model과 +결합하지 않습니다. `funasr-server`, Docker, Kubernetes, vLLM, SGLang, LocalAI, +FunClip 경로는 [MOSS deployment guide](../../docs/moss_transcribe_diarize.md)를 참고하세요. ## API 엔드포인트 diff --git a/examples/openai_api/README_zh.md b/examples/openai_api/README_zh.md index 936bb8f8d..a73617f57 100644 --- a/examples/openai_api/README_zh.md +++ b/examples/openai_api/README_zh.md @@ -89,6 +89,11 @@ curl http://localhost:8000/v1/audio/transcriptions \ | `paraformer` | 120x realtime | 15x realtime | zh/en | 标点恢复 | | `paraformer-en` | 120x realtime | 15x realtime | en | 英文识别 | | `fun-asr-nano` | 17x realtime | 3.6x realtime | 中/英/日 + 中文方言/口音 | LLM-based,时间戳 | +| `moss-transcribe-diarize` | 需在目标 GPU 验证 | 仅离线 | 多语言 | 长音频时间戳 + 匿名说话人标签 | + +MOSS 使用固定 revision 的第三方 HF 模型,不能外挂 VAD 或说话人模型。完整的 +`funasr-server`、Docker Compose、Kubernetes、vLLM、SGLang Omni、LocalAI 与 +FunClip 路径见 [MOSS 部署指南](../../docs/moss_transcribe_diarize_zh.md)。 ## API 端点 diff --git a/examples/openai_api/WORKFLOWS.md b/examples/openai_api/WORKFLOWS.md index b9375c474..43a6d603b 100644 --- a/examples/openai_api/WORKFLOWS.md +++ b/examples/openai_api/WORKFLOWS.md @@ -25,6 +25,11 @@ If the workflow engine runs in Docker, `localhost` usually means the workflow co Before configuring a low-code tool, you can import the [Postman collection](POSTMAN.md) and run health, model-list, and transcription requests from a GUI. For schema-driven imports, use the [OpenAPI spec](OPENAPI.md). Set `FUNASR_BASE_URL`, choose a local audio file for the multipart `file` field, and keep `MODEL_ALIAS=sensevoice` for the first test. +For offline multi-speaker meetings, set `MODEL_ALIAS=moss-transcribe-diarize` +and keep `response_format=verbose_json` so downstream nodes receive native +anonymous speaker segments. Use the [MOSS deployment guide](../../docs/moss_transcribe_diarize.md) +for the isolated GPU service and file-duration limits. + ## Multipart HTTP request Every workflow engine eventually needs to send this request shape: diff --git a/examples/openai_api/WORKFLOWS_zh.md b/examples/openai_api/WORKFLOWS_zh.md index b24fb9556..21979407c 100644 --- a/examples/openai_api/WORKFLOWS_zh.md +++ b/examples/openai_api/WORKFLOWS_zh.md @@ -25,6 +25,10 @@ curl -fsS "$FUNASR_BASE_URL/v1/models" 在配置低代码工具前,可以先导入 [Postman collection](POSTMAN_zh.md),从图形界面跑通 health、模型列表和转写请求;需要按 schema 导入时可使用 [OpenAPI spec](OPENAPI_zh.md)。设置 `FUNASR_BASE_URL`,在 multipart `file` 字段选择本地音频文件,第一次测试建议保持 `MODEL_ALIAS=sensevoice`。 +处理离线多人会议时,将 `MODEL_ALIAS` 改为 `moss-transcribe-diarize`,并保留 +`response_format=verbose_json`,下游节点即可收到模型原生匿名说话人 segments。 +独立 GPU 服务与文件时长边界见 [MOSS 部署指南](../../docs/moss_transcribe_diarize_zh.md)。 + ## Multipart HTTP 请求 所有工作流引擎最终都需要发出下面这种请求: diff --git a/examples/openai_api/docker-compose.moss.yml b/examples/openai_api/docker-compose.moss.yml new file mode 100644 index 000000000..02cecdcec --- /dev/null +++ b/examples/openai_api/docker-compose.moss.yml @@ -0,0 +1,26 @@ +services: + funasr-moss-api: + build: + context: ../.. + dockerfile: examples/openai_api/Dockerfile.moss + image: funasr-moss-api:local + ports: + - "${FUNASR_HOST_PORT:-8000}:8000" + environment: + FUNASR_PORT: "8000" + FUNASR_DEVICE: "${FUNASR_DEVICE:-cuda:0}" + FUNASR_MODEL: moss-transcribe-diarize + HF_HOME: /root/.cache/huggingface + deploy: + resources: + reservations: + devices: + - driver: nvidia + count: 1 + capabilities: [gpu] + volumes: + - funasr-moss-cache:/root/.cache + shm_size: "8gb" + +volumes: + funasr-moss-cache: diff --git a/examples/openai_api/kubernetes/README.md b/examples/openai_api/kubernetes/README.md index 64ee00c5c..818c3810e 100644 --- a/examples/openai_api/kubernetes/README.md +++ b/examples/openai_api/kubernetes/README.md @@ -57,6 +57,11 @@ For in-cluster clients, use `http://funasr-api.speech.svc.cluster.local:8000` as | Memory request | `8Gi` | Tune after observing startup and real audio workloads. | | Startup probe | 10 minutes | Increase if your registry, model hub, or storage backend is slow. | +The `moss-transcribe-diarize` alias uses a separate GPU image and resource profile. Build +`examples/openai_api/Dockerfile.moss`, then apply `funasr-moss-api.yaml` after +replacing `funasr-moss-api:local` with your registry's immutable image digest. +The complete runtime matrix is in the [MOSS deployment guide](../../../docs/moss_transcribe_diarize.md). + ## GPU notes The example Dockerfile is CPU-first. For GPU clusters you need to adapt the image to CUDA-capable PyTorch/FunASR dependencies, then add your cluster's GPU scheduling fields, for example: diff --git a/examples/openai_api/kubernetes/README_zh.md b/examples/openai_api/kubernetes/README_zh.md index 1c9480dfc..b2aa02e88 100644 --- a/examples/openai_api/kubernetes/README_zh.md +++ b/examples/openai_api/kubernetes/README_zh.md @@ -57,6 +57,11 @@ python smoke_test.py --base-url http://localhost:8000 | 内存 request | `8Gi` | 根据启动过程和真实音频负载观测结果调整。 | | Startup probe | 10 分钟 | registry、模型下载或存储后端较慢时增大。 | +`moss-transcribe-diarize` 使用独立的 GPU 镜像与资源配置。先构建 +`examples/openai_api/Dockerfile.moss`,将 `funasr-moss-api.yaml` 中的 +`funasr-moss-api:local` 替换为内部镜像仓库的不可变 digest 后再应用。完整运行矩阵 +见 [MOSS 部署指南](../../../docs/moss_transcribe_diarize_zh.md)。 + ## GPU 说明 示例 Dockerfile 默认面向 CPU。GPU 集群需要先把镜像改成 CUDA-capable PyTorch/FunASR 依赖,再根据集群增加 GPU 调度字段,例如: diff --git a/examples/openai_api/kubernetes/funasr-moss-api.yaml b/examples/openai_api/kubernetes/funasr-moss-api.yaml new file mode 100644 index 000000000..2fa02a4ef --- /dev/null +++ b/examples/openai_api/kubernetes/funasr-moss-api.yaml @@ -0,0 +1,86 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: funasr-moss-cache +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 40Gi +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: funasr-moss-api +spec: + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + app.kubernetes.io/name: funasr-moss-api + template: + metadata: + labels: + app.kubernetes.io/name: funasr-moss-api + spec: + containers: + - name: api + image: funasr-moss-api:local + imagePullPolicy: IfNotPresent + env: + - name: FUNASR_MODEL + value: moss-transcribe-diarize + - name: FUNASR_DEVICE + value: cuda:0 + - name: FUNASR_PORT + value: "8000" + ports: + - name: http + containerPort: 8000 + startupProbe: + httpGet: + path: /health + port: http + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 60 + readinessProbe: + httpGet: + path: /health + port: http + periodSeconds: 10 + resources: + requests: + cpu: "4" + memory: 24Gi + nvidia.com/gpu: "1" + limits: + memory: 48Gi + nvidia.com/gpu: "1" + volumeMounts: + - name: cache + mountPath: /root/.cache + - name: dshm + mountPath: /dev/shm + volumes: + - name: cache + persistentVolumeClaim: + claimName: funasr-moss-cache + - name: dshm + emptyDir: + medium: Memory + sizeLimit: 8Gi +--- +apiVersion: v1 +kind: Service +metadata: + name: funasr-moss-api +spec: + selector: + app.kubernetes.io/name: funasr-moss-api + ports: + - name: http + port: 8000 + targetPort: http diff --git a/examples/openai_api/openapi.json b/examples/openai_api/openapi.json index bcc5c7b8d..67fa1a229 100644 --- a/examples/openai_api/openapi.json +++ b/examples/openai_api/openapi.json @@ -181,7 +181,8 @@ "sensevoice", "paraformer", "paraformer-en", - "fun-asr-nano" + "fun-asr-nano", + "moss-transcribe-diarize" ], "default": "sensevoice", "description": "FunASR model alias." diff --git a/examples/openai_api/server.py b/examples/openai_api/server.py index 47a324c53..625b530e4 100644 --- a/examples/openai_api/server.py +++ b/examples/openai_api/server.py @@ -6,6 +6,7 @@ Works with any agent framework that supports OpenAI audio API. Usage: python server.py --model sensevoice --device cuda --port 8000 + python server.py --model moss-transcribe-diarize --device cuda:0 --port 8000 Then use with any OpenAI-compatible client: curl http://localhost:8000/v1/audio/transcriptions \ @@ -54,6 +55,13 @@ MODEL_CONFIGS = { "vad_model": "fsmn-vad", "vad_kwargs": {"max_single_segment_time": 30000}, }, + "moss-transcribe-diarize": { + "model": "OpenMOSS-Team/MOSS-Transcribe-Diarize", + "model_revision": "e8681d68e7042738ffca8ac8212bc8fcb1131ab8", + "hub": "hf", + "backend": "hf", + "trust_remote_code": True, + }, } @@ -99,7 +107,7 @@ async def transcribe( Accepts the same parameters as OpenAI's /v1/audio/transcriptions: - file: Audio file (wav, mp3, flac, m4a, ogg, webm) - - model: Model to use (sensevoice, paraformer, fun-asr-nano) + - model: Model to use (sensevoice, paraformer, fun-asr-nano, moss-transcribe-diarize) - language: Optional language hint - response_format: json or verbose_json """ diff --git a/funasr/bin/_server_app.py b/funasr/bin/_server_app.py index 17e8ada6e..95db8a63f 100644 --- a/funasr/bin/_server_app.py +++ b/funasr/bin/_server_app.py @@ -1,7 +1,8 @@ """FunASR Server — unified vLLM-based inference service. Provides OpenAI-compatible API (/v1/audio/transcriptions) and REST API (/asr). -Uses vLLM for Fun-ASR-Nano (GPU) or falls back to AutoModel for non-LLM models (SenseVoice/Paraformer). +Uses vLLM for Fun-ASR-Nano (GPU) or AutoModel for supported models, including +MOSS-Transcribe-Diarize's joint transcription and anonymous speaker labels. """ import io @@ -32,6 +33,8 @@ PACKAGE_VERSION = (Path(__file__).resolve().parents[1] / "version.txt").read_tex _LANGUAGE_TAG_RE = re.compile(r"<\|(zh|en|yue|ja|ko)\|>") +MOSS_MODEL_REVISION = "e8681d68e7042738ffca8ac8212bc8fcb1131ab8" +NATIVE_DIARIZATION_MODELS = {"moss-transcribe-diarize"} def extract_language_from_asr_text(text): @@ -257,6 +260,13 @@ def create_app( "vad_model": "fsmn-vad", "punc_model": "ct-punc", }, + "moss-transcribe-diarize": { + "model": "OpenMOSS-Team/MOSS-Transcribe-Diarize", + "model_revision": MOSS_MODEL_REVISION, + "hub": "hf", + "backend": "hf", + "trust_remote_code": True, + }, } def _load_spk_model(): @@ -335,7 +345,7 @@ def create_app( if app.state.model_path: cfg["model"] = app.state.model_path cfg["hub"] = app.state.hub - elif app.state.hub: + elif app.state.hub and "hub" not in cfg: cfg["hub"] = app.state.hub cfg["device"] = device cfg["disable_update"] = True @@ -434,7 +444,7 @@ def create_app( segments.append(segment) if not segments and text: segments = build_openai_fallback_segments(text, duration) - if use_spk and segments: + if use_spk and model_name not in NATIVE_DIARIZATION_MODELS and segments: audio_data, sr = sf.read(audio_path) attach_speaker_labels( audio_data, diff --git a/funasr/bin/server.py b/funasr/bin/server.py index 5aefda2e1..3d2d594b6 100644 --- a/funasr/bin/server.py +++ b/funasr/bin/server.py @@ -5,6 +5,7 @@ Usage: funasr-server # default: sensevoice on cuda:0, port 8000 funasr-server --device cpu --port 9000 funasr-server --model paraformer + funasr-server --model moss-transcribe-diarize --device cuda:0 funasr-server --model-path /path/to/local/model funasr-server --model-path username/paraformer --hub hf funasr-server --cors-origin http://localhost:3000 @@ -31,6 +32,7 @@ Examples: funasr-server # Start with SenseVoice on GPU funasr-server --device cpu # Start on CPU funasr-server --model paraformer # Use Paraformer model + funasr-server --model moss-transcribe-diarize --device cuda:0 funasr-server --port 9000 # Custom port funasr-server --model-path /path/to/local/model # Use local model funasr-server --model-path username/model --hub hf # Use HuggingFace model @@ -45,7 +47,14 @@ Then use with OpenAI SDK: parser.add_argument("--host", default="0.0.0.0", help="Bind address (default: 0.0.0.0)") parser.add_argument("--port", type=int, default=8000, help="Port (default: 8000)") parser.add_argument("--device", default="cuda", help="Device: cuda, cpu, mps (default: cuda)") - parser.add_argument("--model", default="auto", help="Pre-load model: auto (GPU=fun-asr-nano, CPU=sensevoice), sensevoice, paraformer, fun-asr-nano") + parser.add_argument( + "--model", + default="auto", + help=( + "Pre-load model: auto (GPU=fun-asr-nano, CPU=sensevoice), " + "sensevoice, paraformer, fun-asr-nano, moss-transcribe-diarize" + ), + ) parser.add_argument("--model-path", default=None, help="Local model path or model ID (overrides --model)") parser.add_argument("--hub", default="ms", help="Model hub: ms (ModelScope), hf (HuggingFace) (default: ms)") parser.add_argument( diff --git a/tests/test_moss_transcribe_diarize_docs.py b/tests/test_moss_transcribe_diarize_docs.py index 234740899..cfc7a1379 100644 --- a/tests/test_moss_transcribe_diarize_docs.py +++ b/tests/test_moss_transcribe_diarize_docs.py @@ -1,3 +1,4 @@ +import json from pathlib import Path import pytest @@ -78,3 +79,82 @@ def test_all_deployment_matrices_link_moss_guide(matrix: Path) -> None: "FunASR 소유 model 또는 `AutoModel` backend가 아닙니다", ): assert stale_claim not in moss_row + + +def test_moss_guides_cover_funasr_service_and_offline_boundaries() -> None: + for guide in GUIDES: + text = guide.read_text(encoding="utf-8") + assert "funasr-server --model moss-transcribe-diarize" in text + assert "response_format=verbose_json" in text + assert "examples/openai_api/docker-compose.moss.yml" in text + assert "examples/openai_api/kubernetes/funasr-moss-api.yaml" in text + assert "WebSocket" in text + assert "FunClip" in text + + +def test_moss_service_recipes_are_pinned_and_gpu_scoped() -> None: + dockerfile = ROOT / "examples" / "openai_api" / "Dockerfile.moss" + compose = ROOT / "examples" / "openai_api" / "docker-compose.moss.yml" + kubernetes = ( + ROOT / "examples" / "openai_api" / "kubernetes" / "funasr-moss-api.yaml" + ) + + assert dockerfile.is_file() + assert compose.is_file() + assert kubernetes.is_file() + docker_text = dockerfile.read_text(encoding="utf-8") + compose_text = compose.read_text(encoding="utf-8") + kubernetes_text = kubernetes.read_text(encoding="utf-8") + for text in (docker_text, compose_text, kubernetes_text): + assert "moss-transcribe-diarize" in text + assert "transformers>=5.6,<6" in docker_text + assert "capabilities: [gpu]" in compose_text + assert "nvidia.com/gpu" in kubernetes_text + + +def test_github_pages_indexes_moss_deployment_guide() -> None: + index = (ROOT / "docs" / "index.rst").read_text(encoding="utf-8") + assert "./moss_transcribe_diarize.md" in index + assert "./moss_transcribe_diarize_zh.md" in index + + +def test_readmes_link_the_runnable_moss_service() -> None: + readmes = [ + ROOT / "README.md", + ROOT / "README_zh.md", + ROOT / "README_ja.md", + ROOT / "README_ko.md", + ROOT / "examples" / "openai_api" / "README.md", + ROOT / "examples" / "openai_api" / "README_zh.md", + ROOT / "examples" / "openai_api" / "README_ja.md", + ROOT / "examples" / "openai_api" / "README_ko.md", + ] + for readme in readmes: + text = readme.read_text(encoding="utf-8") + assert "moss-transcribe-diarize" in text + assert "moss_transcribe_diarize" in text + + +def test_openai_consumer_docs_expose_moss_alias_and_boundaries() -> None: + paths = [ + "CLIENTS.md", + "GRADIO.md", + "GRADIO_zh.md", + "OPENAPI.md", + "OPENAPI_zh.md", + "WORKFLOWS.md", + "WORKFLOWS_zh.md", + "JAVASCRIPT.md", + "JAVASCRIPT_zh.md", + "kubernetes/README.md", + "kubernetes/README_zh.md", + ] + root = ROOT / "examples" / "openai_api" + for relative in paths: + text = (root / relative).read_text(encoding="utf-8") + assert "moss-transcribe-diarize" in text + assert "moss_transcribe_diarize" in text + + spec = json.loads((root / "openapi.json").read_text(encoding="utf-8")) + model = spec["components"]["schemas"]["TranscriptionRequest"]["properties"]["model"] + assert "moss-transcribe-diarize" in model["enum"] diff --git a/tests/test_server_app_openai_segments.py b/tests/test_server_app_openai_segments.py index 972b27dac..f97dedadf 100644 --- a/tests/test_server_app_openai_segments.py +++ b/tests/test_server_app_openai_segments.py @@ -442,6 +442,53 @@ def test_spk_request_lazily_loads_and_reuses_speaker_model(monkeypatch): ] +def test_moss_service_uses_pinned_joint_transcription_config(monkeypatch): + module = load_server_app(monkeypatch) + DummyAutoModel = install_dummy_funasr(monkeypatch) + + module.create_app(device="cuda:0", preload_model="moss-transcribe-diarize") + + config = DummyAutoModel.instances[-1] + assert config["model"] == "OpenMOSS-Team/MOSS-Transcribe-Diarize" + assert config["model_revision"] == "e8681d68e7042738ffca8ac8212bc8fcb1131ab8" + assert config["hub"] == "hf" + assert config["backend"] == "hf" + assert config["trust_remote_code"] is True + assert "vad_model" not in config + assert "spk_model" not in config + + +def test_moss_verbose_json_preserves_native_speaker_segments(monkeypatch): + module = load_server_app(monkeypatch) + DummyAutoModel = install_dummy_funasr( + monkeypatch, + generated_result={ + "text": "hello again", + "sentence_info": [ + {"start": 100, "end": 900, "spk": "S01", "text": "hello"}, + {"start": 950, "end": 1700, "spk": "S02", "sentence": "again"}, + ], + }, + ) + monkeypatch.setattr(module.sf, "info", lambda path: types.SimpleNamespace(duration=1.7)) + app = module.create_app(device="cuda:0", preload_model="moss-transcribe-diarize") + transcribe = app.routes[("POST", "/v1/audio/transcriptions")] + + response = asyncio.run( + transcribe( + file=DummyUpload(), + model="moss-transcribe-diarize", + language=None, + response_format="verbose_json", + spk=True, + ) + ) + + assert [segment["speaker"] for segment in response["segments"]] == ["S01", "S02"] + assert [segment["text"] for segment in response["segments"]] == ["hello", "again"] + assert not any(config.get("model") == "cam++" for config in DummyAutoModel.instances) + + def test_server_versions_follow_package_version(monkeypatch): expected = (REPO_ROOT / "funasr" / "version.txt").read_text().strip() module = load_server_app(monkeypatch) diff --git a/web-pages/product-site/data/deployments.json b/web-pages/product-site/data/deployments.json index 952c97497..877bd03c8 100644 --- a/web-pages/product-site/data/deployments.json +++ b/web-pages/product-site/data/deployments.json @@ -1,6 +1,6 @@ { "schema_version": 1, - "verified": "2026-08-30", + "verified": "2026-09-01", "deployments": [ { "id": "moss-transcribe-diarize", @@ -13,7 +13,7 @@ "models": ["OpenMOSS-Team/MOSS-Transcribe-Diarize (third-party Apache-2.0 model)"], "operating_systems": ["Linux"], "interfaces": ["OpenAI-compatible HTTP", "vLLM", "SGLang Omni", "Transformers", "LocalAI / moss-transcribe.cpp"], - "tested": {"funasr": "AutoModel vLLM + SGLang adapters; third-party model@e8681d68", "runtime": "vLLM 0.27.1 / Torch 2.13.0+cu129 / H100 80GB", "verified": "2026-08-30"}, + "tested": {"funasr": "AutoModel HF + vLLM + SGLang adapters and OpenAI HTTP service; third-party model@e8681d68", "runtime": "Transformers 5.16.0.dev0 + Torch 2.11.0+cu130 and vLLM 0.27.1 + Torch 2.13.0+cu129 / H100 80GB", "verified": "2026-09-01"}, "commands": { "install": [ "uv venv --python 3.12 .venv-moss && curl -fL https://github.com/vllm-project/vllm/releases/download/v0.27.1/vllm-0.27.1%2Bcu129-cp38-abi3-manylinux_2_28_x86_64.whl -o vllm-0.27.1+cu129-cp38-abi3-manylinux_2_28_x86_64.whl && echo 'bf0d52faa2a51e7a01c6856a7a8a2d1307fd0ff711415d34168a67ffac0fa47b vllm-0.27.1+cu129-cp38-abi3-manylinux_2_28_x86_64.whl' | sha256sum -c - && uv pip install --python .venv-moss/bin/python --torch-backend=auto \"vllm[audio] @ file://$PWD/vllm-0.27.1+cu129-cp38-abi3-manylinux_2_28_x86_64.whl\"", @@ -33,6 +33,31 @@ ] }, "runtime_paths": [ + { + "id": "funasr-server", + "tested": "Transformers 5.16.0.dev0 / Torch 2.11.0+cu130 / H100 80GB; real HTTP response verified 2026-09-01", + "translations": { + "zh": {"name": "FunASR OpenAI 兼容服务", "summary": "直接使用内置 funasr-server 加载固定 revision 的 HF 后端,返回带匿名说话人标签的 verbose_json;可使用专用 Docker Compose 与 Kubernetes GPU 配方。"}, + "en": {"name": "FunASR OpenAI-compatible service", "summary": "Load the pinned HF backend directly with funasr-server and return verbose_json with anonymous speaker labels; dedicated Docker Compose and Kubernetes GPU recipes are included."} + }, + "commands": { + "install": [ + "python -m pip install 'transformers>=5.6,<6' fastapi uvicorn python-multipart" + ], + "launch": [ + "funasr-server --model moss-transcribe-diarize --device cuda:0 --port 8000", + "docker compose -f examples/openai_api/docker-compose.moss.yml up --build" + ], + "health": [ + "curl -fsS http://127.0.0.1:8000/health", + "curl -fsS http://127.0.0.1:8000/v1/models" + ], + "smoke": [ + "curl -fsS http://127.0.0.1:8000/v1/audio/transcriptions -F file=@runtime/llama.cpp/tests/sample.wav -F model=moss-transcribe-diarize -F response_format=verbose_json | tee /tmp/funasr-moss-transcription.json", + "python - <<'PY'\nimport json\n\nwith open('/tmp/funasr-moss-transcription.json', encoding='utf-8') as stream:\n payload = json.load(stream)\nsegments = payload.get('segments', [])\nassert payload.get('text', '').strip() and segments, payload\nassert all(item.get('speaker') and item.get('start') <= item.get('end') for item in segments), payload\nprint(payload['text'], sorted({item['speaker'] for item in segments}))\nPY" + ] + } + }, { "id": "vllm", "tested": "vLLM 0.27.1 / Torch 2.13.0+cu129 / H100 80GB; FunASR adapter verified", diff --git a/web-pages/product-site/tests/test_output.py b/web-pages/product-site/tests/test_output.py index e470f7797..6974762a6 100644 --- a/web-pages/product-site/tests/test_output.py +++ b/web-pages/product-site/tests/test_output.py @@ -128,7 +128,7 @@ def test_detail_commands_come_from_registry(built_site): assert command in rendered -def test_moss_detail_renders_separate_vllm_and_sglang_runtime_paths(built_site): +def test_moss_detail_renders_all_service_runtime_paths(built_site): for relative in ( 'deploy/moss-transcribe-diarize.html', 'en/deploy/moss-transcribe-diarize.html', @@ -136,8 +136,14 @@ def test_moss_detail_renders_separate_vllm_and_sglang_runtime_paths(built_site): soup = read_soup(built_site / relative) paths = soup.select('[data-runtime-path]') - assert [path['data-runtime-path'] for path in paths] == ['vllm', 'sglang-omni'] + assert [path['data-runtime-path'] for path in paths] == [ + 'funasr-server', + 'vllm', + 'sglang-omni', + ] rendered = '\n'.join(path.get_text('\n') for path in paths) + assert 'funasr-server --model moss-transcribe-diarize' in rendered + assert 'docker-compose.moss.yml' in rendered assert 'vllm serve OpenMOSS-Team/MOSS-Transcribe-Diarize' in rendered assert 'sgl-omni serve' in rendered assert 'response_format=diarized_json' in rendered diff --git a/web-pages/product-site/tests/test_registry.py b/web-pages/product-site/tests/test_registry.py index 007af2f38..8d568fcbd 100644 --- a/web-pages/product-site/tests/test_registry.py +++ b/web-pages/product-site/tests/test_registry.py @@ -107,15 +107,24 @@ def test_moss_transcribe_diarize_contract_tracks_third_party_upstream(valid_regi 'OpenMOSS-Team/MOSS-Transcribe-Diarize (third-party Apache-2.0 model)' ] assert entry['tested'] == { - 'funasr': 'AutoModel vLLM + SGLang adapters; third-party model@e8681d68', - 'runtime': 'vLLM 0.27.1 / Torch 2.13.0+cu129 / H100 80GB', - 'verified': '2026-08-30', + 'funasr': 'AutoModel HF + vLLM + SGLang adapters and OpenAI HTTP service; third-party model@e8681d68', + 'runtime': 'Transformers 5.16.0.dev0 + Torch 2.11.0+cu130 and vLLM 0.27.1 + Torch 2.13.0+cu129 / H100 80GB', + 'verified': '2026-09-01', } assert 'LocalAI / moss-transcribe.cpp' in entry['interfaces'] assert {'cpu', 'desktop-edge-gpu'} <= set(entry['hardware']) runtime_paths = {path['id']: path for path in entry['runtime_paths']} - assert set(runtime_paths) == {'vllm', 'sglang-omni'} + assert set(runtime_paths) == {'funasr-server', 'vllm', 'sglang-omni'} + service_commands = '\n'.join( + command + for group in ('install', 'launch', 'health', 'smoke') + for command in runtime_paths['funasr-server']['commands'][group] + ) + assert 'funasr-server --model moss-transcribe-diarize' in service_commands + assert 'docker-compose.moss.yml' in service_commands + assert 'response_format=verbose_json' in service_commands + assert 'real HTTP response verified 2026-09-01' in runtime_paths['funasr-server']['tested'] assert runtime_paths['vllm']['tested'] == ( 'vLLM 0.27.1 / Torch 2.13.0+cu129 / H100 80GB; FunASR adapter verified' ) @@ -222,7 +231,7 @@ def test_audio_cpp_contract_tracks_mainline_nano_and_sensevoice(valid_registry): entry = next(item for item in valid_registry['deployments'] if item['id'] == 'audio-cpp') llama_cpp = next(item for item in valid_registry['deployments'] if item['id'] == 'llama-cpp') - assert valid_registry['verified'] == '2026-08-30' + assert valid_registry['verified'] == '2026-09-01' assert entry['maturity'] == 'community-verified' assert entry['selector_rank'] > llama_cpp['selector_rank'] assert entry['tested'] == { @@ -391,7 +400,7 @@ def test_sensevoice_native_server_contract_tracks_merged_runtime(valid_registry) llama_cpp = next(item for item in valid_registry['deployments'] if item['id'] == 'llama-cpp') audio_cpp = next(item for item in valid_registry['deployments'] if item['id'] == 'audio-cpp') - assert valid_registry['verified'] == '2026-08-30' + assert valid_registry['verified'] == '2026-09-01' assert entry['maturity'] == 'production-verified' assert llama_cpp['selector_rank'] < entry['selector_rank'] < audio_cpp['selector_rank'] assert entry['tested'] == { @@ -485,9 +494,9 @@ def test_runtime_paths_require_unique_ids_translations_and_commands(valid_regist item for item in data['deployments'] if item['id'] == 'moss-transcribe-diarize' ) - entry['runtime_paths'][1]['id'] = 'vllm' - del entry['runtime_paths'][0]['translations']['en']['summary'] - del entry['runtime_paths'][1]['commands']['health'] + entry['runtime_paths'][2]['id'] = 'vllm' + del entry['runtime_paths'][1]['translations']['en']['summary'] + del entry['runtime_paths'][2]['commands']['health'] errors = validate_registry(data)