docs: align four-language HTTP service setup and response contracts

Signed-off-by: zhifu gao <[email protected]>
This commit is contained in:
zhifu gao
2026-09-07 15:03:23 +00:00
parent 50fddb748f
commit 8d1f5b64db
5 changed files with 484 additions and 157 deletions
+49 -35
View File
@@ -6,36 +6,47 @@ An OpenAI-style `/v1/audio/transcriptions` endpoint for private speech transcrip
This page starts the repository's [example server](server.py). The packaged `funasr-server` is a [different implementation](../../funasr/bin/_server_app.py); see [API boundaries](#api-contract) before reusing its settings. For in-process `AutoModel.generate()` rather than HTTP requests, use the [Python SDK guide](../../docs/python_api.md) ([中文](../../docs/python_api_zh.md)).
For the maintained packaged-service route, start with [Agent integration](../../docs/agent_integration.md). The example has **no built-in authentication or upload limit**; `api_key="not-needed"` does not authenticate it. Keep local testing on loopback. Before sharing, configure TLS, gateway authentication, upload/time/rate limits, audio/transcript retention, and private health/model/schema access using the [security guide](SECURITY.md).
## API Contract
| Entry point | Startup model | Omitted multipart `model` | Speaker opt-in |
|---|---|---|---|
| `python server.py` in this directory | `sensevoice` | `sensevoice` | No `spk` form field; only preserves speaker labels already returned by the model. |
| `funasr-server` | `--model auto`: `fun-asr-nano` for a device string starting with `cuda`, otherwise `sensevoice` | `fun-asr-nano` | `spk=true` requests the separate speaker pipeline for non-native diarization models; default is `False`. |
- **Example `python server.py` in this directory:** startup and omitted multipart `model` both default to `sensevoice`. There is no `spk` form field; the example only preserves speaker labels already returned by the model.
- **Packaged `funasr-server`:** startup `--model auto` selects `fun-asr-nano` for a device string starting with `cuda`, otherwise `sensevoice`. Omitted multipart `model` independently defaults to `fun-asr-nano`. `spk=true` requests the separate speaker pipeline for non-native diarization models; default is `False`.
Specify `model` explicitly in requests: startup preloading and the request default are different settings. Query the deployed `/v1/models`; for example, `paraformer-en` is registered by this example but is not a built-in alias of the packaged server. Verify fields with the running `/openapi.json`, not just the checked-in [example schema](OPENAPI.md).
`response_format=verbose_json` selects a response shape; **it does not enable diarization or force timestamp generation**. This example copies `sentence_info` into `segments` if present, otherwise returns `segments=[]`. Speaker labels can be absent or null. MOSS supplies native anonymous labels; it does not need `spk=true` or external VAD/CAM++.
SDK output such as `timestamp`, or Nano's `timestamps` / `ctc_timestamps`, is not automatically converted into HTTP segments. This example accepts multipart `file`, `model`, `language`, and `response_format`; SDK options such as `use_itn`, hotwords, raw arrays, and `spk` are not its form fields. Its `language` is the submitted hint or `auto`, not detected language; the packaged service can use backend language detection.
In this example, `duration` is elapsed time around `generate()` in seconds, excluding initial model loading; it is **not audio duration**. The packaged server's verbose response uses audio duration in seconds (its fallback can use 0 when audio metadata is unavailable). Segment `start`/`end` use seconds in both services. The packaged fallback can synthesize coarse segments from text and audio duration; those are not word-level forced alignment. Its verbose schema includes `task` and per-segment `id`/`words`, while this example includes `model`; do not assume identical JSON fields. See [response examples and speaker requests](CLIENTS.md#api-contract).
## Quick Start
From the repository root:
Use a fresh checkout and a POSIX shell with Python 3.11 installed:
```bash
pip install funasr fastapi uvicorn python-multipart
git clone https://github.com/modelscope/FunASR.git FunASR-api
cd FunASR-api
git checkout --detach e19029adca384a06a2f60bd8c18cb98f1a0499aa
python3.11 -m venv .venv
source .venv/bin/activate
python -m pip install -e .
python -m pip install fastapi uvicorn python-multipart
python -m pip check
cd examples/openai_api
python server.py --model sensevoice --device cuda --port 8000
python server.py --host 127.0.0.1 --model sensevoice --device cpu --port 8000
```
Wait for model loading before checking `GET /health`; download and startup time depend on the checkpoint, cache, network, and hardware. The commands below use this directory unless stated otherwise.
This pins source only, not dependencies, model weights, audio decoders, or CUDA. A PyPI install alone does not provide repository examples. These are setup instructions, not evidence of a fresh installation or successful acoustic inference on your hardware.
Wait for model loading before checking `GET /health`; download and startup time depend on the checkpoint, cache, network, and hardware. Health alone does not verify transcription. The commands below use this directory unless stated otherwise. After preparing CUDA-capable dependencies, replace the CPU command with `python server.py --host 127.0.0.1 --model sensevoice --device cuda --port 8000`; do not start both on the same port.
Need copy-paste integration snippets for Python SDK, JavaScript/TypeScript, HTTP clients, agent tools, a browser demo, Postman, OpenAPI imports, Kubernetes deployment, or Dify/n8n-style workflows? See [Client recipes](CLIENTS.md), [JavaScript/TypeScript recipes](JAVASCRIPT.md), [Gradio browser demo](GRADIO.md), [workflow recipes](WORKFLOWS.md), the [Chinese workflow recipes](WORKFLOWS_zh.md), the [Postman collection](POSTMAN.md), the [OpenAPI spec](OPENAPI.md), the [security and gateway guide](SECURITY.md), and the [Kubernetes deployment template](kubernetes/README.md).
### End-to-end smoke test
In another terminal, download a public sample and verify both health and transcription:
In another terminal, enter the same checkout, activate `.venv`, and enter `examples/openai_api`. The optional scripts check health and transcription:
```bash
bash smoke_test.sh
@@ -43,11 +54,13 @@ bash smoke_test.sh
python smoke_test.py
```
Equivalent manual commands:
Equivalent manual commands using public Chinese sample audio, not a Japanese/Korean validation set:
```bash
curl -L https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/BAC009S0764W0121.wav -o sample.wav
curl http://localhost:8000/health
curl http://localhost:8000/v1/models
curl http://localhost:8000/openapi.json
curl http://localhost:8000/v1/audio/transcriptions \
-F file=@sample.wav \
-F model=sensevoice \
@@ -59,7 +72,7 @@ curl http://localhost:8000/v1/audio/transcriptions \
If you want a local browser UI for upload or microphone testing, run the API server first and then launch the optional Gradio frontend:
```bash
pip install gradio
python -m pip install gradio
python gradio_app.py --base-url http://localhost:8000
```
@@ -67,7 +80,7 @@ The browser demo calls the same OpenAI-compatible API endpoints as the smoke tes
## Usage with OpenAI SDK (Python)
Install the separate HTTP client with `pip install openai`. This is not the FunASR Python SDK.
In the same activated environment, install the separate HTTP client with `python -m pip install openai`. This is not the FunASR Python SDK. Replace `meeting.wav` with a real local audio file supported by your prepared decoders.
```python
from openai import OpenAI
@@ -75,18 +88,15 @@ from openai import OpenAI
client = OpenAI(base_url="http://localhost:8000/v1", api_key="not-needed")
# Basic transcription
result = client.audio.transcriptions.create(
model="sensevoice", # or "paraformer", "paraformer-en", "fun-asr-nano"
file=open("meeting.wav", "rb"),
)
with open("meeting.wav", "rb") as audio:
result = client.audio.transcriptions.create(model="sensevoice", file=audio)
print(result.text)
# Inspect the verbose response; segments may be empty
result = client.audio.transcriptions.create(
model="sensevoice",
file=open("meeting.wav", "rb"),
response_format="verbose_json",
)
with open("meeting.wav", "rb") as audio:
result = client.audio.transcriptions.create(
model="sensevoice", file=audio, response_format="verbose_json",
)
# verbose_json does not enable diarization; see API Contract above
print(getattr(result, "segments", []))
```
@@ -109,20 +119,21 @@ curl http://localhost:8000/v1/audio/transcriptions \
These are aliases in this example's `MODEL_CONFIGS`, not a universal SDK or server model list. The endpoint removes rich `<|...|>` tags from returned text; it does not expose dedicated emotion/event fields.
| Model | Example configuration | Response limits |
|---|---|---|
| `sensevoice` | SenseVoiceSmall + FSMN-VAD | Does not enable sentence timestamps or external speaker clustering by default. |
| `paraformer` | `paraformer-zh` + FSMN-VAD + CT punctuation | Punctuation is configured; `verbose_json` alone does not request sentence records. |
| `paraformer-en` | `paraformer-en` + FSMN-VAD | Example-only alias relative to the packaged server; no punctuation component configured here. |
| `fun-asr-nano` | Fun-ASR-Nano via `AutoModel`, HF hub + FSMN-VAD | Not a vLLM route in this example. CTC timestamp availability depends on complete checkpoint weights. |
| `moss-transcribe-diarize` | Third-party OpenMOSS native transcription/diarization adapter | Requires its separate dependency environment; preserves model-provided timestamps and anonymous labels. |
- `sensevoice`: SenseVoiceSmall + FSMN-VAD. Does not enable sentence timestamps or external speaker clustering by default.
- `paraformer`: `paraformer-zh` + FSMN-VAD + CT punctuation. Punctuation is configured; `verbose_json` alone does not request sentence records.
- `paraformer-en`: `paraformer-en` + FSMN-VAD. Example-only alias relative to the packaged server; no punctuation component configured here.
- `fun-asr-nano`: Fun-ASR-Nano via `AutoModel`, HF hub + FSMN-VAD. Not a vLLM route in this example. CTC timestamp availability depends on complete checkpoint weights.
- `moss-transcribe-diarize`: Third-party OpenMOSS native transcription/diarization adapter. Requires its separate dependency environment; preserves model-provided timestamps and anonymous labels.
For checkpoint-specific language and license information, use [model selection](../../docs/model_selection.md) and the model's own license. FunASR software's MIT license is not a license for every model weight. Benchmark the selected route on your own workload; these aliases do not define universal speed or capacity.
Fun-ASR-MLT-Nano is a separate multilingual checkpoint, not a built-in alias in either service; base Nano does not establish Korean support. For a custom checkpoint, the packaged route uses `--model-path` and `--hub` with request `model="custom"`; these are not example-server options. Follow the model-selection and Agent guides for checkpoint-specific setup.
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.
Its speaker labels are anonymous within one recording, not real-world identities or cross-recording speaker recognition. An alias appearing in `/v1/models` does not prove its dependencies or weights are ready.
## API Endpoints
@@ -139,6 +150,8 @@ Prefer no-code API checks? Use the [Gradio browser demo](GRADIO.md) for local up
The multipart HTTP/tool-function pattern can be used to integrate **LangChain**, **LlamaIndex**, **AutoGen**, **CrewAI**, **Semantic Kernel**, **Dify**, and **n8n**; validate the chosen integration against this service's supported fields. These recipes do not establish compatibility with every framework version or realtime API. See [Client recipes](CLIENTS.md) and [JavaScript/TypeScript recipes](JAVASCRIPT.md) for SDK and agent-tool patterns, plus [workflow recipes](WORKFLOWS.md) for low-code HTTP nodes and webhook workers ([中文](WORKFLOWS_zh.md)).
Both services map the workflow request alias `whisper-1` to the startup-selected model; this does not run OpenAI Whisper. A workflow container's `localhost` refers to that container. Use an intentionally authorized reachable gateway/service address, not unrestricted public exposure.
### LangChain Example
```python
from openai import OpenAI
@@ -147,9 +160,8 @@ client = OpenAI(base_url="http://localhost:8000/v1", api_key="x")
def transcribe_for_agent(audio_path: str) -> str:
"""Tool function for LangChain agent."""
result = client.audio.transcriptions.create(
model="sensevoice", file=open(audio_path, "rb")
)
with open(audio_path, "rb") as audio:
result = client.audio.transcriptions.create(model="sensevoice", file=audio)
return result.text
```
@@ -157,11 +169,13 @@ def transcribe_for_agent(audio_path: str) -> str:
From the repository root, build the example image with the following commands. The default image starts the example `server.py` in CPU mode, not the packaged `funasr-server`.
This is a local-development publication setting, not authentication. The container still listens on `0.0.0.0`; only the host-published port binds to `127.0.0.1`. Do not change the container listener to loopback. The current Dockerfile installs unpinned PyPI FunASR/dependencies while copying this example, so it is not the source-pinned Python environment above or a reproducible acoustic environment.
```bash
cd examples/openai_api
cp .env.example .env
docker compose up --build
FUNASR_HOST_PORT=127.0.0.1:8000 docker compose up --build
```
Equivalent one-off `docker run` command:
@@ -169,7 +183,7 @@ Equivalent one-off `docker run` command:
```bash
docker build -t funasr-api .
docker run --rm -p 8000:8000 \
docker run --rm -p 127.0.0.1:8000:8000 \
-e FUNASR_DEVICE=cpu \
-e FUNASR_MODEL=sensevoice \
funasr-api
@@ -178,7 +192,7 @@ docker run --rm -p 8000:8000 \
For GPU hosts, use NVIDIA Container Toolkit and a CUDA-capable PyTorch/FunASR image. After adapting the image dependencies for CUDA, run the same server with `FUNASR_DEVICE=cuda`:
```bash
docker run --rm --gpus all -p 8000:8000 \
docker run --rm --gpus all -p 127.0.0.1:8000:8000 \
-e FUNASR_DEVICE=cuda \
-e FUNASR_MODEL=sensevoice \
funasr-api
@@ -191,7 +205,7 @@ BASE_URL=http://localhost:8000 bash smoke_test.sh
python smoke_test.py --base-url http://localhost:8000
```
For a single build/run/smoke command, use `bash validate_docker.sh` for the portable CPU image. On a host with NVIDIA Container Toolkit and a CUDA-capable image, use `bash validate_docker.sh --gpu` to run the same smoke test with `FUNASR_DEVICE=cuda`.
The optional [validate_docker.sh](validate_docker.sh) combines build/run/smoke steps, but **its default port publication uses all host interfaces** and does not inherit the loopback settings above. Review its networking before running it; use the explicit loopback build/run/smoke recipe above for local testing on shared networks. Its GPU mode additionally requires NVIDIA Container Toolkit and a CUDA-capable image. These instructions are not evidence of a Docker or acoustic inference test.
## Kubernetes Deployment
+122 -44
View File
@@ -2,22 +2,70 @@
# FunASR OpenAI 互換 API サーバー
FunASR OpenAI 互換 API は `/v1/audio/transcriptions` を提供します。OpenAI スタイルの SDK、エージェントフレームワーク、Dify、n8n、HTTP ノード、社内システムから、プライベートな音声認識サービスとして利用できます。
FunASR OpenAI 互換 API は、音声ファイルを multipart HTTP で送る `/v1/audio/transcriptions` を提供します。これは音声転写用の OpenAI API の一部であり、API 全体、リアルタイム API、すべての SDK やフレームワーク機能への互換性を保証するものではありません。
本ページはリポジトリ内の[サンプルサービス](server.py)の手順です。パッケージ付属の `funasr-server` は[別の実装](../../funasr/bin/_server_app.py)であり、設定を流用する前に[API 契約](#api-contract)を確認してください。[日本語 Agent ガイド](../../docs/agent_integration_ja.md)はパッケージ付属サービスや各種連携の入口です。プロセス内で `AutoModel.generate()` を呼び出す場合は、Python SDK ガイドの[英語版](../../docs/python_api.md)または[中国語版](../../docs/python_api_zh.md)を参照してください。
**ローカル開発から始めてください。** このサービスには認証機能やアプリケーション側のアップロードサイズ上限が組み込まれておらず、SDK の仮の API キーも認証しません。下記ではホストの loopback に明示的にバインドします。共有・公開する前に、[セキュリティとゲートウェイガイド](SECURITY.md)に従い、TLS、認証、アップロードサイズ制限、レート制限、クライアントとゲートウェイのタイムアウトを設けてください。音声・転写文の保存期間とアクセス権を決め、`/health`、`/v1/models`、`/openapi.json` と Swagger UI `/docs` へのアクセスも制限します。CORS は認証の代わりにはなりません。
## API Contract
- **サンプル `server.py`**: 起動時のプリロードと、multipart `model` 省略時の既定値はどちらも `sensevoice` です。フォームは `file`、`model`、`language`、`response_format` を受け取ります。以下では `json` または `verbose_json` を使います。
- **パッケージ付属 `funasr-server`**: CLI の既定値 `--model auto` は、デバイス文字列が `cuda` で始まる場合に `fun-asr-nano`、それ以外では `sensevoice` を選びます。一方、multipart `model` を省略すると、プリロードとは独立して `fun-asr-nano` になります。
リクエストには毎回 `model` を明示し、稼働中の `/v1/models` と `/openapi.json` を確認してください。`paraformer-en` はサンプルサービスに登録されていますが、パッケージ付属サービスの組み込み別名ではありません。パッケージ付属サービスでカスタムモデルを使う場合は `--model-path` と適切な `--hub` を設定し、リクエストは `model="custom"` とします。これらの CLI オプションはサンプル `server.py` にはありません。任意の checkpoint ID を `--model` に渡せるという意味でもありません。
`response_format=verbose_json` は応答形式の選択であり、**話者分離やタイムスタンプ生成を有効にするスイッチではありません**。サンプルサービスはモデルが返した `sentence_info` のみを `segments` に変換し、なければ `segments=[]` を返します。SDK の `timestamps` や `ctc_timestamps` が存在しても、それらを直接 HTTP の `segments` として返す処理ではありません。Nano の CTC 時刻は必要な学習済み重みが揃っていることに依存し、HTTP 経由で常に取得できるわけではありません。
サンプルには `spk` フォームフィールドがありません。`spk=true` を送っても外部話者処理は有効になりません。パッケージ付属サービスでは `spk=true` により、ネイティブ話者分離モデル以外に対して別の話者処理を要求できます。既定値は `False` で、対応する依存環境とモデルが必要です。MOSS のネイティブ出力は録音内の匿名ラベルであり、実在する人物の識別や別録音間の同一人物判定ではありません。MOSS に外部 VAD や話者モデルを追加しないでください。
両サービスの `start` / `end` は秒単位です。SDK の `sentence_info` に含まれるミリ秒の座標は、HTTP アダプターで秒に変換されます。サンプルの `duration` は `generate()` の処理時間で、初回モデルロードを含まず、**音声の長さではありません**。サンプルの `language` は送信したヒント、未指定なら `auto` であり、言語検出結果とは限りません。パッケージ付属サービスの `duration` は音声の長さで、fallback 経路ではメタデータを読めない場合に 0 になることがあります。`language` は `auto` 以外の明示したヒントを優先し、それがなければ取得できた検出結果を使います。パッケージ付属サービスの fallback はテキストと音声の長さから粗い区間を合成する場合があり、単語単位の強制アラインメントではありません。
パッケージ付属の verbose 応答は `task` と区間ごとの `id` / `words` を含み、サンプルは `model` を含みます。話者フィールドは欠落または null の場合があります。共通の完全な JSON スキーマとは考えず、[応答例と話者リクエスト](CLIENTS.md#api-contract)を確認してください。`language` ヒントの意味もモデル依存です。SDK の `use_itn`、`hotwords`、キャッシュ、配列入力などはこの HTTP フォームのオプションではなく、`AutoModel.generate()` の全機能が公開されているわけではありません。
## クイックスタート
POSIX シェルと Python 3.11 を使い、新しいチェックアウトと仮想環境を作成します。PyPI パッケージのインストールだけでは、このリポジトリのサンプルファイルは配置されません。
```bash
pip install funasr fastapi uvicorn python-multipart
python server.py --model sensevoice --device cuda --port 8000
git clone https://github.com/modelscope/FunASR.git FunASR-api
cd FunASR-api
git checkout --detach e19029adca384a06a2f60bd8c18cb98f1a0499aa
python3.11 -m venv .venv
source .venv/bin/activate
python -m pip install -e .
python -m pip install fastapi uvicorn python-multipart
python -m pip check
cd examples/openai_api
python server.py --host 127.0.0.1 --model sensevoice --device cpu --port 8000
```
モデルのロード後にサービスが起動します。ヘルスチェックは `GET /health` です。
固定するのはソースの revision です。依存パッケージ、モデル重み、音声デコーダー、CUDA 環境まで固定する手順ではありません。[インストールガイド](../../docs/installation/installation.md)に従って対象環境を準備してください。バージョン表示が `1.4.14` でも、公開済み PyPI パッケージと修正済みソースが同じ内容とは限りません。`pip check` は依存宣言の整合性を調べるもので、新規インストールや音声推論の成功を証明しません。
CUDA を利用する場合は対応する PyTorch、ドライバーなどを準備し、CPU サーバーを停止してから、同じ仮想環境と `examples/openai_api` ディレクトリで次の代替コマンドを実行します。同じポートで両方を同時起動しないでください。
```bash
python server.py --host 127.0.0.1 --model sensevoice --device cuda --port 8000
```
モデルのロードを待ち、サーバー用ターミナルはそのままにします。初回ダウンロードや起動時間は checkpoint、キャッシュ、ネットワーク、ハードウェアに依存します。以下のクライアント操作は別のターミナルで行います。最初に `git clone` を実行した親ディレクトリから、同じ環境と作業ディレクトリに入ってください。
```bash
cd FunASR-api
source .venv/bin/activate
cd examples/openai_api
curl -fsS http://localhost:8000/health
curl -fsS http://localhost:8000/v1/models
curl -fsS http://localhost:8000/openapi.json
```
ヘルスチェックやモデル一覧の成功だけでは、音声転写の成功は確認できません。別途記載がない限り、以降のクライアントコマンドはこのディレクトリで実行します。
コピーして使える連携例が必要な場合は、[クライアントレシピ](CLIENTS.md)、[JavaScript/TypeScript レシピ](JAVASCRIPT.md)、[Gradio ブラウザデモ](GRADIO.md)、[ワークフローレシピ](WORKFLOWS.md)、[Postman コレクション](POSTMAN.md)、[OpenAPI 仕様](OPENAPI.md)、[セキュリティとゲートウェイガイド](SECURITY.md)、[Kubernetes デプロイテンプレート](kubernetes/README.md)を参照してください。
### エンドツーエンド smoke test
別のターミナルで実行します。
上記で準備したクライアント用ターミナルで、次のどちらかを実行できます。これは検証手順であり、対象環境での実行済み結果を示すものではありません。
```bash
bash smoke_test.sh
@@ -25,12 +73,12 @@ bash smoke_test.sh
python smoke_test.py
```
同等の手動コマンド:
手動で確認する場合は、次の公開サンプルを利用できます。これは**中国語の音声**であり、このサンプルの転写は日本語や韓国語の精度検証ではありません。
```bash
curl -L https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/BAC009S0764W0121.wav -o sample.wav
curl http://localhost:8000/health
curl http://localhost:8000/v1/audio/transcriptions \
curl -fL https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/BAC009S0764W0121.wav -o sample.wav
curl -fsS http://localhost:8000/health
curl -fsS http://localhost:8000/v1/audio/transcriptions \
-F file=@sample.wav \
-F model=sensevoice \
-F response_format=verbose_json
@@ -41,41 +89,55 @@ curl http://localhost:8000/v1/audio/transcriptions \
ローカルブラウザで音声ファイルのアップロードやマイク入力を試したい場合は、先に API サーバーを起動し、オプションの Gradio フロントエンドを起動します。
```bash
pip install gradio
python -m pip install gradio
python gradio_app.py --base-url http://localhost:8000
```
このブラウザデモは smoke test と同じ OpenAI 互換 API エンドポイントを呼び出します。Docker、Kubernetes、本番利用の注意点は [Gradio ブラウザデモ](GRADIO.md)を参照してください。
このブラウザデモは smoke test と同じ API エンドポイントを呼び出す別のフロントエンドであり、API に認証を追加するものではありません。マイク権限、Docker、Kubernetes、本番利用の注意点は [Gradio ブラウザデモ](GRADIO.md)を参照してください。
## OpenAI SDK で使う
OpenAI の HTTP クライアントを別途インストールします。これは FunASR のプロセス内 Python SDK ではありません。
```bash
python -m pip install openai
```
`meeting.wav` を実際のローカル音声ファイルに置き換えて実行します。デコード可能な形式は準備した音声依存環境にも依存します。仮の `api_key` は SDK の必須引数を満たすための値で、サンプルサービスによる認証ではありません。
```python
from openai import OpenAI
client = OpenAI(base_url="http://localhost:8000/v1", api_key="not-needed")
result = client.audio.transcriptions.create(
model="sensevoice", # "paraformer", "paraformer-en", "fun-asr-nano" も利用できます
file=open("meeting.wav", "rb"),
)
with open("meeting.wav", "rb") as audio:
result = client.audio.transcriptions.create(
model="sensevoice",
file=audio,
)
print(result.text)
verbose = client.audio.transcriptions.create(
model="sensevoice",
file=open("meeting.wav", "rb"),
response_format="verbose_json",
)
print(verbose.segments)
with open("meeting.wav", "rb") as audio:
verbose = client.audio.transcriptions.create(
model="sensevoice",
file=audio,
response_format="verbose_json",
)
print(getattr(verbose, "segments", []))
```
区間配列は空でも構いません。区間が返っても、そのことだけで正確な字幕時刻や話者分離が確認できるわけではありません。[API 契約](#api-contract)を参照してください。
## curl で使う
`audio.wav` は実際のローカル音声ファイルに置き換えてください。JSON に配列や URL を入れるのではなく、`file` にバイナリファイルを multipart で送ります。
```bash
curl http://localhost:8000/v1/audio/transcriptions \
curl -fsS http://localhost:8000/v1/audio/transcriptions \
-F file=@audio.wav \
-F model=sensevoice
curl http://localhost:8000/v1/audio/transcriptions \
curl -fsS http://localhost:8000/v1/audio/transcriptions \
-F file=@audio.wav \
-F model=sensevoice \
-F response_format=verbose_json
@@ -83,17 +145,19 @@ curl http://localhost:8000/v1/audio/transcriptions \
## 利用できるモデル
| Model | GPU 速度 | CPU 速度 | 言語 | 特徴 |
|---|---|---|---|---|
| `sensevoice` | 170x realtime | 17x realtime | zh/en/ja/ko/yue | 感情・イベントタグ |
| `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 |
以下はサンプル `server.py` の `MODEL_CONFIGS` にある 5 つの別名です。SDK 全体やすべてのサーバーの共通モデル一覧ではなく、一定の処理速度や同時実行数を保証する表でもありません。
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 をまとめています。
- `sensevoice`: SenseVoiceSmall + FSMN-VAD。既定では文単位のタイムスタンプや外部話者クラスタリングを有効にしません。
- `paraformer`: `paraformer-zh` + FSMN-VAD + CT 句読点モデル。句読点処理は設定されていますが、`verbose_json` だけでは文単位の記録を要求しません。
- `paraformer-en`: `paraformer-en` + FSMN-VAD。このサンプルには句読点モデルの設定がなく、パッケージ付属サービスの組み込み別名でもありません。
- `fun-asr-nano`: HF の Fun-ASR-Nano を `AutoModel` + FSMN-VAD で使用します。このサンプルは vLLM 経路ではなく、CTC 時刻が HTTP 区間として返る保証もありません。
- `moss-transcribe-diarize`: 第三者 OpenMOSS のネイティブ転写・話者分離アダプター。専用の依存環境が必要で、モデルが返した時刻と匿名話者ラベルを保持します。
SenseVoice の HTTP テキストからは `<|...|>` のリッチタグが除去されます。感情・イベントの専用フィールドを返す API ではありません。生のモデル出力が必要な場合はモデル別の Python SDK 契約を確認してください。
基本の Fun-ASR-Nano は中国語・英語・日本語と中国語方言・地域アクセントを対象とする経路であり、韓国語対応を含むという意味ではありません。31 言語の **Fun-ASR-MLT-Nano は別の checkpoint**で、この 2 つのサービスの組み込み別名ではありません。対象言語とモデル重みのライセンスは[モデル選択ガイド](../../docs/model_selection_ja.md)と個別のモデルカードで確認してください。FunASR ソフトウェアの MIT ライセンスは、すべてのモデル重みのライセンスを意味しません。
MOSS は固定 revision の third-party HF model を使用し、外部 VAD / speaker model とは併用しません。ラベルは録音内の匿名話者を示すもので、人物の身元を検証するものではありません。[MOSS deployment guide](../../docs/moss_transcribe_diarize.md) に `funasr-server`、Docker、Kubernetes、vLLM、SGLang Omni、LocalAI、FunClip の個別経路をまとめています。別名が一覧に存在するだけでは、全モデルのロード済み状態や依存環境の互換性は確認できません。
## API エンドポイント
@@ -103,12 +167,17 @@ MOSS は固定 revision の third-party HF model を使用し、外部 VAD / spe
| `/v1/models` | GET | モデルエイリアスの一覧 |
| `/health` | GET | ヘルスチェック、ロード済みモデル、利用可能モデル |
| `/docs` | GET | FastAPI Swagger ドキュメント |
| `/openapi.json` | GET | 稼働中のサービスのスキーマ |
コードを書かずに確認したい場合は、[Gradio ブラウザデモ](GRADIO.md)でローカルアップロードやマイク入力を試すか、[Postman コレクション](POSTMAN.md)をインポートしてください。API ゲートウェイ、開発者ポータル、社内クライアント生成には [OpenAPI 仕様](OPENAPI.md)を利用できます。
コードを書かずに確認したい場合は、[Gradio ブラウザデモ](GRADIO.md)でローカルアップロードやマイク入力を試すか、[Postman コレクション](POSTMAN.md)をインポートしてください。[OpenAPI 仕様](OPENAPI.md)はサンプル用の資料であり、パッケージ付属サービスの全フィールドを表すものではありません。API ゲートウェイやクライアント生成には稼働中のスキーマも確認してください。
## エージェントとローコードワークフロー
**LangChain**、**LlamaIndex**、**AutoGen**、**CrewAI**、**Semantic Kernel**、**Dify**、**n8n**、OpenAI audio API または multipart HTTP を使える任意のシステムで利用できます。
**LangChain**、**LlamaIndex**、**AutoGen**、**CrewAI**、**Semantic Kernel**、**Dify**、**n8n** などには、multipart HTTP またはツール関数を介して接続できます。利用するバージョンと本サービスのフィールドで個別に確認してください。全フレームワーク機能やリアルタイム API の互換性を保証するものではありません。
両サービスは、n8n 向けの特別なリクエスト別名 `whisper-1` を、起動時に選択したモデルへ対応付けます。これは OpenAI Whisper を実行する指定ではありません。パッケージ付属サービスで `--model-path` を指定している場合は `custom` に対応します。通常の HTTP ノードでは `model` を明示し、この互換別名を任意のモデル切替機構として扱わないでください。
Dify/n8n のファイルは multipart のバイナリ `file` として渡します。コンテナ内の `localhost` はそのコンテナ自身です。認可されたサービス名や到達可能なホストを設定し、接続問題の回避策として無認証の公開サービスにしないでください。URL を取得する worker を使う場合は、取得先の検証やサイズ制限も別途必要です。
- SDK、JavaScript/TypeScript、Agent tool の書き方は [クライアントレシピ](CLIENTS.md) と [JavaScript/TypeScript レシピ](JAVASCRIPT.md)を参照してください。
- Dify、n8n、HTTP ノード、webhook worker は [ワークフローレシピ](WORKFLOWS.md)を参照してください。
@@ -117,21 +186,25 @@ MOSS は固定 revision の third-party HF model を使用し、外部 VAD / spe
## Docker デプロイ
デフォルトのイメージは CPU モードで起動し、再現しやすい smoke test として使えます。
リポジトリのルートから次のコマンドを実行します。すでにホスト側のサーバーを起動している場合は停止し、ポートを空けてください。既定のイメージはサンプル `server.py` を CPU モードで起動し、パッケージ付属の `funasr-server` は起動しません。
Dockerfile はバージョン未固定の PyPI FunASR と依存パッケージをインストールして、チェックアウトの `server.py` をコピーします。上のソース固定・editable install と同じ環境ではなく、再現可能なモデル検証済みイメージとも限りません。
```bash
cd examples/openai_api
cp .env.example .env
docker compose up --build
FUNASR_HOST_PORT=127.0.0.1:8000 docker compose up --build
```
同等の `docker run`:
この POSIX シェルのプレフィックスは、既存 Compose ファイルのホスト側公開ポートを loopback に限定します。コンテナ内部のリスナーは `0.0.0.0` のままにしてください。コンテナのリスナーを `127.0.0.1` に変更することとは異なります。ホスト側 loopback はローカル開発用の設定であり、認証やネットワーク全体の安全性を保証するものではありません。
Compose と同時に起動しない場合の `docker run`:
```bash
docker build -t funasr-api .
docker run --rm -p 8000:8000 \
docker run --rm -p 127.0.0.1:8000:8000 \
-e FUNASR_DEVICE=cpu \
-e FUNASR_MODEL=sensevoice \
funasr-api
@@ -140,22 +213,24 @@ docker run --rm -p 8000:8000 \
GPU ホストでは NVIDIA Container Toolkit と CUDA 対応の PyTorch/FunASR イメージが必要です。CUDA 依存関係に合わせてイメージを調整した後、次のように起動できます。
```bash
docker run --rm --gpus all -p 8000:8000 \
docker run --rm --gpus all -p 127.0.0.1:8000:8000 \
-e FUNASR_DEVICE=cuda \
-e FUNASR_MODEL=sensevoice \
funasr-api
```
コンテナの検証:
コンテナ起動後、同じ環境とサンプルディレクトリを用意した別のターミナルで、いずれかの方法で確認できます:
```bash
BASE_URL=http://localhost:8000 bash smoke_test.sh
python smoke_test.py --base-url http://localhost:8000
```
追加の build/run/smoke 手順は [validate_docker.sh](validate_docker.sh) にありますが、**既定ではホストの全インターフェースにポートを公開**し、上記の loopback 設定を引き継ぎません。実行前にネットワーク設定を確認し、共有ネットワークでのローカル検証には上記の明示的な loopback 手順を使ってください。GPU モードには CUDA 対応イメージと NVIDIA Container Toolkit が必要です。このページの記述は Docker や音声推論の実行済み検証結果ではありません。
## Kubernetes デプロイ
チーム内で共有したりゲートウェイ経由で公開したりする前に、[セキュリティとゲートウェイガイド](SECURITY.md)を確認し、TLS、認証、アップロード制限、レート制限、ログ方針を整えてください。
チーム内で共有したりゲートウェイ経由で公開したりする前に、[セキュリティとゲートウェイガイド](SECURITY.md)を確認し、TLS、認証、アップロード制限、レート制限、タイムアウト、ログと保存期間の方針を整えてください。
永続化されたモデルキャッシュ、ヘルスプローブ、プライベート `ClusterIP` を持つ内部クラスタサービスが必要な場合は、[Kubernetes デプロイテンプレート](kubernetes/README.md)から始めてください。サンプルイメージをビルドして push し、manifests を適用した後、`kubectl port-forward` と `python smoke_test.py --base-url http://localhost:8000` で検証します。
@@ -163,6 +238,8 @@ CUDA 対応イメージと GPU スケジューリング設定が整うまでは
## 設定
以下はサンプル `server.py` の既定値であり、`funasr-server` の既定値ではありません。[API 契約](#api-contract)と比較してください。ローカル手順では安全のため `--host 127.0.0.1` と `--device cpu` を明示しています。
| 引数 | デフォルト | 説明 |
|---|---|---|
| `--host` | `0.0.0.0` | バインドアドレス |
@@ -177,6 +254,7 @@ Docker 環境変数:
| `FUNASR_PORT` | `8000` | `server.py` に渡すコンテナポート |
| `FUNASR_DEVICE` | `cpu` | コンテナのデバイスモード。CUDA 対応依存関係を持つイメージでのみ `cuda` に設定してください |
| `FUNASR_MODEL` | `sensevoice` | コンテナ起動時にロードするモデルエイリアス |
| `FUNASR_HOST_PORT` | `8000` | Compose のホスト側ポート指定。上のローカル手順では `127.0.0.1:8000` を指定します。 |
## トラブルシューティング
@@ -185,5 +263,5 @@ Docker 環境変数:
| CUDA が利用できない | まず `--device cpu` で smoke test を通します。 |
| 8000 ポートが使用中 | `--port 9000` に変更し、`BASE_URL=http://localhost:9000 bash smoke_test.sh` または `python smoke_test.py --base-url http://localhost:9000` を実行します。 |
| モデルのダウンロードが遅い | 安定したネットワークで再試行するか、ModelScope/Hugging Face から事前にモデルをダウンロードします。 |
| Dify/n8n コンテナから `localhost` に接続できない | ワークフロー実行環境から到達できるホスト名、Compose service name、または Kubernetes service name を使います。 |
| 応答に `segments` がない | `response_format=verbose_json` を設定します。 |
| Dify/n8n コンテナから `localhost` に接続できない | 認可された到達先のホスト名、Compose service name、または Kubernetes service name を使い、アクセス制御を維持します。 |
| 応答に `segments` がない | `response_format=verbose_json` で区間フィールドのある形式を選べますが、配列は空の場合があります。タイムスタンプや話者分離を有効にする指定ではありません。[API 契約](#api-contract)を確認してください。 |
+122 -44
View File
@@ -2,22 +2,70 @@
# FunASR OpenAI 호환 API 서버
FunASR OpenAI 호환 API는 `/v1/audio/transcriptions`를 제공합니다. OpenAI 스타일 SDK, 에이전트 프레임워크, Dify, n8n, HTTP 노드, 내부 업무 시스템에서 프라이빗 음성 인식 서비스로 사용할 수 있습니다.
FunASR OpenAI 호환 API는 음성 파일을 multipart HTTP로 보내는 `/v1/audio/transcriptions`를 제공합니다. 음성 전사용 OpenAI API의 일부를 구현한 것이며, 전체 API, 실시간 API 또는 모든 SDK와 프레임워크 기능의 호환성을 보장하지 않습니다.
이 페이지는 저장소의 [예제 서비스](server.py)를 실행하는 방법입니다. 패키지의 `funasr-server`는 [다른 구현](../../funasr/bin/_server_app.py)이므로 설정을 재사용하기 전에 [API 계약](#api-contract)을 확인하세요. [한국어 Agent 가이드](../../docs/agent_integration_ko.md)는 패키지 서비스와 여러 연동 방식의 시작점입니다. 프로세스 안에서 `AutoModel.generate()`를 호출하려면 Python SDK 가이드의 [영문판](../../docs/python_api.md) 또는 [중문판](../../docs/python_api_zh.md)을 참고하세요.
**로컬 개발 환경에서 시작하세요.** 이 서비스에는 인증 기능이나 애플리케이션 수준의 업로드 크기 상한이 내장되어 있지 않으며 SDK의 임시 API 키도 인증하지 않습니다. 아래 명령은 호스트의 loopback 주소에 명시적으로 바인딩합니다. 공유하거나 공개하기 전에 [보안 및 게이트웨이 가이드](SECURITY.md)에 따라 TLS, 인증, 업로드 크기 제한, 요청 속도 제한, 클라이언트와 게이트웨이의 타임아웃을 설정하세요. 음성 및 전사문의 보관 기간과 접근 권한을 정하고 `/health`, `/v1/models`, `/openapi.json` 및 Swagger UI `/docs`에 대한 접근도 제한하세요. CORS는 인증을 대신하지 않습니다.
## API Contract
- **예제 `server.py`**: 시작 시 미리 로드하는 모델과 multipart `model` 생략 시 기본값은 모두 `sensevoice`입니다. 폼은 `file`, `model`, `language`, `response_format`을 받습니다. 아래에서는 `json` 또는 `verbose_json`을 사용합니다.
- **패키지 `funasr-server`**: CLI 기본값인 `--model auto`는 장치 문자열이 `cuda`로 시작하면 `fun-asr-nano`, 아니면 `sensevoice`를 선택합니다. 반면 multipart `model`을 생략하면 미리 로드한 모델과 관계없이 `fun-asr-nano`가 선택됩니다.
요청마다 `model`을 명시하고 실행 중인 `/v1/models`와 `/openapi.json`을 확인하세요. `paraformer-en`은 예제 서비스에 등록되어 있지만 패키지 서비스의 내장 별칭은 아닙니다. 패키지 서비스에서 사용자 지정 모델을 쓰려면 `--model-path`와 적절한 `--hub`를 설정하고 요청에 `model="custom"`을 사용하세요. 이 CLI 옵션들은 예제 `server.py`에는 없습니다. 임의의 checkpoint ID를 `--model`에 넣을 수 있다는 뜻도 아닙니다.
`response_format=verbose_json`은 응답 형식 선택이며, **화자 분리나 타임스탬프 생성을 켜는 옵션이 아닙니다**. 예제 서비스는 모델이 반환한 `sentence_info`만 `segments`로 변환하고, 없으면 `segments=[]`를 반환합니다. SDK에 `timestamps`나 `ctc_timestamps`가 있어도 이를 직접 HTTP `segments`로 내보내는 처리는 아닙니다. Nano의 CTC 시각 정보는 필요한 학습 가중치가 모두 있는지에 따라 달라지며, HTTP에서 항상 얻을 수 있는 것은 아닙니다.
예제에는 `spk` 폼 필드가 없습니다. `spk=true`를 보내도 외부 화자 처리가 활성화되지 않습니다. 패키지 서비스에서는 `spk=true`로 네이티브 화자 분리 모델 외의 모델에 별도 화자 처리를 요청할 수 있습니다. 기본값은 `False`이며 해당 의존 환경과 모델이 필요합니다. MOSS의 네이티브 출력은 녹음 안의 익명 라벨이지 실제 인물의 신원이나 서로 다른 녹음 사이의 동일 인물 판정이 아닙니다. MOSS에 외부 VAD나 화자 모델을 추가하지 마세요.
두 서비스의 `start` / `end` 단위는 초입니다. SDK의 `sentence_info`에 있는 밀리초 좌표는 HTTP 어댑터에서 초로 변환합니다. 예제의 `duration`은 첫 모델 로드를 제외한 `generate()` 처리 시간이며, **음성 길이가 아닙니다**. 예제의 `language`는 전달한 힌트 또는 생략 시 `auto`이며 언어 감지 결과라고 볼 수 없습니다. 패키지 서비스의 `duration`은 음성 길이이고, fallback 경로에서 메타데이터를 읽지 못하면 0이 될 수 있습니다. `language`는 `auto`가 아닌 명시적 힌트를 우선하며, 없으면 얻을 수 있는 감지 결과를 사용합니다. 패키지 서비스의 fallback은 텍스트와 음성 길이로 대략적인 구간을 만들 수도 있으므로 단어 단위 강제 정렬 결과가 아닙니다.
패키지의 verbose 응답에는 `task`와 구간별 `id` / `words`가 있고, 예제 응답에는 `model`이 있습니다. 화자 필드는 없거나 null일 수 있습니다. 완전히 같은 JSON 스키마라고 가정하지 말고 [응답 예시와 화자 요청](CLIENTS.md#api-contract)을 확인하세요. `language` 힌트의 의미도 모델에 따라 다릅니다. SDK의 `use_itn`, `hotwords`, 캐시, 배열 입력 등은 이 HTTP 폼의 옵션이 아니며 `AutoModel.generate()`의 모든 기능이 노출되는 것은 아닙니다.
## 빠른 시작
POSIX 셸과 Python 3.11을 사용해 새 체크아웃과 가상 환경을 만드세요. PyPI 패키지만 설치하면 저장소의 예제 파일은 함께 배치되지 않습니다.
```bash
pip install funasr fastapi uvicorn python-multipart
python server.py --model sensevoice --device cuda --port 8000
git clone https://github.com/modelscope/FunASR.git FunASR-api
cd FunASR-api
git checkout --detach e19029adca384a06a2f60bd8c18cb98f1a0499aa
python3.11 -m venv .venv
source .venv/bin/activate
python -m pip install -e .
python -m pip install fastapi uvicorn python-multipart
python -m pip check
cd examples/openai_api
python server.py --host 127.0.0.1 --model sensevoice --device cpu --port 8000
```
모델 로드가 끝나면 서비스가 시작됩니다. 헬스 체크는 `GET /health`입니다.
이 절차는 소스 revision만 고정합니다. 의존 패키지, 모델 가중치, 오디오 디코더, CUDA 환경까지 고정하지는 않습니다. [설치 가이드](../../docs/installation/installation.md)에 따라 대상 환경을 준비하세요. 버전이 `1.4.14`로 표시되어도 배포된 PyPI 패키지와 수정된 소스의 내용이 같다는 뜻은 아닙니다. `pip check`는 의존성 선언의 일관성을 확인하며, 새 환경 설치나 음성 추론의 성공을 증명하지 않습니다.
CUDA를 사용하려면 호환되는 PyTorch와 드라이버 등을 준비한 뒤 CPU 서버를 중지하고, 같은 가상 환경의 `examples/openai_api` 디렉터리에서 다음 대체 명령을 실행하세요. 같은 포트에 두 서버를 동시에 실행하지 마세요.
```bash
python server.py --host 127.0.0.1 --model sensevoice --device cuda --port 8000
```
모델 로드가 끝날 때까지 기다리고 서버 터미널은 유지하세요. 첫 다운로드와 시작 시간은 checkpoint, 캐시, 네트워크, 하드웨어에 따라 달라집니다. 이후 클라이언트 작업은 다른 터미널에서 합니다. 처음 `git clone`을 실행한 상위 디렉터리에서 같은 환경과 작업 디렉터리로 들어가세요.
```bash
cd FunASR-api
source .venv/bin/activate
cd examples/openai_api
curl -fsS http://localhost:8000/health
curl -fsS http://localhost:8000/v1/models
curl -fsS http://localhost:8000/openapi.json
```
상태 확인이나 모델 목록 조회의 성공만으로 음성 전사가 성공했는지는 알 수 없습니다. 별도 설명이 없으면 이후 클라이언트 명령은 이 디렉터리에서 실행하세요.
바로 복사해서 쓸 수 있는 연동 예제가 필요하면 [클라이언트 레시피](CLIENTS.md), [JavaScript/TypeScript 레시피](JAVASCRIPT.md), [Gradio 브라우저 데모](GRADIO.md), [워크플로 레시피](WORKFLOWS.md), [Postman 컬렉션](POSTMAN.md), [OpenAPI 명세](OPENAPI.md), [보안 및 게이트웨이 가이드](SECURITY.md), [Kubernetes 배포 템플릿](kubernetes/README.md)을 참고하세요.
### 엔드투엔드 smoke test
다른 터미널에서 실행합니다.
위에서 준비한 클라이언트 터미널에서 다음 중 하나를 실행할 수 있습니다. 이는 검증 절차이며 대상 환경에서 이미 실행한 결과를 제시하는 것이 아닙니다.
```bash
bash smoke_test.sh
@@ -25,12 +73,12 @@ bash smoke_test.sh
python smoke_test.py
```
동일한 수동 명령:
수동으로 확인하려면 다음 공개 샘플을 사용할 수 있습니다. 이 파일은 **중국어 음성**이며, 이 샘플의 전사는 일본어나 한국어 정확도 검증이 아닙니다.
```bash
curl -L https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/BAC009S0764W0121.wav -o sample.wav
curl http://localhost:8000/health
curl http://localhost:8000/v1/audio/transcriptions \
curl -fL https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/BAC009S0764W0121.wav -o sample.wav
curl -fsS http://localhost:8000/health
curl -fsS http://localhost:8000/v1/audio/transcriptions \
-F file=@sample.wav \
-F model=sensevoice \
-F response_format=verbose_json
@@ -41,41 +89,55 @@ curl http://localhost:8000/v1/audio/transcriptions \
로컬 브라우저에서 파일 업로드나 마이크 입력을 테스트하려면 먼저 API 서버를 시작한 뒤 선택 사항인 Gradio 프런트엔드를 실행합니다.
```bash
pip install gradio
python -m pip install gradio
python gradio_app.py --base-url http://localhost:8000
```
이 브라우저 데모는 smoke test와 동일한 OpenAI 호환 API 엔드포인트를 호출합니다. Docker, Kubernetes, 프로덕션 참고 사항은 [Gradio 브라우저 데모](GRADIO.md)를 확인하세요.
이 브라우저 데모는 smoke test와 같은 API 엔드포인트를 호출하는 별도 프런트엔드이며 API에 인증을 추가하지 않습니다. 마이크 권한, Docker, Kubernetes, 프로덕션 참고 사항은 [Gradio 브라우저 데모](GRADIO.md)를 확인하세요.
## OpenAI SDK로 사용하기
OpenAI HTTP 클라이언트를 별도로 설치하세요. 이것은 FunASR의 프로세스 내 Python SDK가 아닙니다.
```bash
python -m pip install openai
```
`meeting.wav`를 실제 로컬 음성 파일로 바꿔 실행하세요. 디코딩 가능한 형식은 준비한 오디오 의존 환경에도 영향을 받습니다. 임시 `api_key`는 SDK의 필수 인자를 채우는 값이지 예제 서비스가 수행하는 인증이 아닙니다.
```python
from openai import OpenAI
client = OpenAI(base_url="http://localhost:8000/v1", api_key="not-needed")
result = client.audio.transcriptions.create(
model="sensevoice", # "paraformer", "paraformer-en", "fun-asr-nano"도 사용할 수 있습니다
file=open("meeting.wav", "rb"),
)
with open("meeting.wav", "rb") as audio:
result = client.audio.transcriptions.create(
model="sensevoice",
file=audio,
)
print(result.text)
verbose = client.audio.transcriptions.create(
model="sensevoice",
file=open("meeting.wav", "rb"),
response_format="verbose_json",
)
print(verbose.segments)
with open("meeting.wav", "rb") as audio:
verbose = client.audio.transcriptions.create(
model="sensevoice",
file=audio,
response_format="verbose_json",
)
print(getattr(verbose, "segments", []))
```
구간 배열은 비어 있을 수 있습니다. 구간이 반환되어도 그것만으로 정확한 자막 시각이나 화자 분리를 확인할 수는 없습니다. [API 계약](#api-contract)을 참고하세요.
## curl로 사용하기
`audio.wav`는 실제 로컬 음성 파일로 바꾸세요. JSON에 배열이나 URL을 넣는 방식이 아니라, `file`에 바이너리 파일을 multipart로 보냅니다.
```bash
curl http://localhost:8000/v1/audio/transcriptions \
curl -fsS http://localhost:8000/v1/audio/transcriptions \
-F file=@audio.wav \
-F model=sensevoice
curl http://localhost:8000/v1/audio/transcriptions \
curl -fsS http://localhost:8000/v1/audio/transcriptions \
-F file=@audio.wav \
-F model=sensevoice \
-F response_format=verbose_json
@@ -83,17 +145,19 @@ curl http://localhost:8000/v1/audio/transcriptions \
## 사용 가능한 모델
| Model | GPU 속도 | CPU 속도 | 언어 | 특징 |
|---|---|---|---|---|
| `sensevoice` | 170x realtime | 17x realtime | zh/en/ja/ko/yue | 감정 및 이벤트 태그 |
| `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 |
다음은 예제 `server.py`의 `MODEL_CONFIGS`에 있는 5개 별칭입니다. 전체 SDK나 모든 서버의 공통 모델 목록이 아니며, 일정한 처리 속도나 동시 처리량을 보장하는 표도 아닙니다.
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)를 참고하세요.
- `sensevoice`: SenseVoiceSmall + FSMN-VAD. 기본적으로 문장 타임스탬프나 외부 화자 클러스터링을 활성화하지 않습니다.
- `paraformer`: `paraformer-zh` + FSMN-VAD + CT 문장부호 모델. 문장부호 처리가 설정되어 있지만 `verbose_json`만으로 문장별 기록을 요청하지는 않습니다.
- `paraformer-en`: `paraformer-en` + FSMN-VAD. 이 예제에는 문장부호 모델이 설정되어 있지 않으며 패키지 서비스의 내장 별칭도 아닙니다.
- `fun-asr-nano`: HF의 Fun-ASR-Nano를 `AutoModel` + FSMN-VAD로 사용합니다. 이 예제는 vLLM 경로가 아니며 CTC 시각이 HTTP 구간으로 반환된다는 보장도 없습니다.
- `moss-transcribe-diarize`: 제3자 OpenMOSS의 네이티브 전사 및 화자 분리 어댑터. 별도 의존 환경이 필요하며 모델이 반환한 시각과 익명 화자 라벨을 유지합니다.
SenseVoice의 HTTP 텍스트에서는 `<|...|>` 리치 태그가 제거됩니다. 감정이나 이벤트를 전용 필드로 반환하는 API가 아닙니다. 원시 모델 출력이 필요하면 모델별 Python SDK 계약을 확인하세요.
기본 Fun-ASR-Nano는 중국어·영어·일본어 및 중국어 방언·지역 억양을 다루는 경로이며 한국어 지원을 포함한다는 뜻이 아닙니다. 31개 언어를 지원하는 **Fun-ASR-MLT-Nano는 별도 checkpoint**이고, 두 서비스의 내장 별칭이 아닙니다. 대상 언어와 모델 가중치의 라이선스는 [모델 선택 가이드](../../docs/model_selection_ko.md)와 개별 모델 카드에서 확인하세요. FunASR 소프트웨어의 MIT 라이선스가 모든 모델 가중치의 라이선스를 뜻하지는 않습니다.
MOSS는 고정 revision의 third-party HF model을 사용하며 외부 VAD / speaker model과 결합하지 않습니다. 라벨은 녹음 안의 익명 화자를 나타내며 사람의 신원을 검증하지 않습니다. `funasr-server`, Docker, Kubernetes, vLLM, SGLang Omni, LocalAI, FunClip의 개별 경로는 [MOSS deployment guide](../../docs/moss_transcribe_diarize.md)를 참고하세요. 별칭이 목록에 있다는 것만으로 모든 모델의 로드 완료나 의존 환경의 호환성을 확인할 수는 없습니다.
## API 엔드포인트
@@ -103,12 +167,17 @@ FunClip 경로는 [MOSS deployment guide](../../docs/moss_transcribe_diarize.md)
| `/v1/models` | GET | 모델 별칭 목록 |
| `/health` | GET | 헬스 체크, 로드된 모델, 사용 가능한 모델 |
| `/docs` | GET | FastAPI Swagger 문서 |
| `/openapi.json` | GET | 실행 중인 서비스의 스키마 |
코드 작성 없이 확인하려면 [Gradio 브라우저 데모](GRADIO.md)로 로컬 업로드나 마이크 테스트를 진행하거나 [Postman 컬렉션](POSTMAN.md)을 가져오세요. API 게이트웨이, 개발자 포털, 내부 클라이언트 생성을 위해서는 [OpenAPI 명세](OPENAPI.md)를 사용할 수 있습니다.
코드 작성 없이 확인하려면 [Gradio 브라우저 데모](GRADIO.md)로 로컬 업로드나 마이크 테스트를 진행하거나 [Postman 컬렉션](POSTMAN.md)을 가져오세요. [OpenAPI 명세](OPENAPI.md)는 예제용 자료이지 패키지 서비스의 모든 필드를 설명하는 자료가 아닙니다. API 게이트웨이나 클라이언트 생성에는 실행 중인 스키마도 확인하세요.
## 에이전트 및 로우코드 워크플로
**LangChain**, **LlamaIndex**, **AutoGen**, **CrewAI**, **Semantic Kernel**, **Dify**, **n8n**, OpenAI audio API 또는 multipart HTTP를 지원하는 모든 시스템에서 사용할 수 있습니다.
**LangChain**, **LlamaIndex**, **AutoGen**, **CrewAI**, **Semantic Kernel**, **Dify**, **n8n** 등에는 multipart HTTP 또는 도구 함수를 통해 연결할 수 있습니다. 사용하는 버전과 이 서비스의 필드를 기준으로 개별 확인하세요. 모든 프레임워크 기능이나 실시간 API의 호환성을 보장하지 않습니다.
두 서비스는 n8n용 특수 요청 별칭 `whisper-1`을 시작 시 선택한 모델로 매핑합니다. OpenAI Whisper를 실행하라는 지정이 아닙니다. 패키지 서비스에 `--model-path`를 지정했다면 `custom`으로 매핑됩니다. 일반 HTTP 노드에서는 `model`을 명시하고 이 호환 별칭을 임의의 모델 전환 기능으로 취급하지 마세요.
Dify/n8n의 파일은 multipart 바이너리 `file`로 전달합니다. 컨테이너 안의 `localhost`는 그 컨테이너 자신입니다. 허가된 서비스 이름이나 접근 가능한 호스트를 설정하고, 연결 문제를 해결하려고 인증 없이 공개하지 마세요. URL을 가져오는 worker를 사용한다면 대상 검증과 크기 제한도 별도로 필요합니다.
- SDK, JavaScript/TypeScript, Agent tool 작성법은 [클라이언트 레시피](CLIENTS.md)와 [JavaScript/TypeScript 레시피](JAVASCRIPT.md)를 참고하세요.
- Dify, n8n, HTTP 노드, webhook worker는 [워크플로 레시피](WORKFLOWS.md)를 참고하세요.
@@ -117,21 +186,25 @@ FunClip 경로는 [MOSS deployment guide](../../docs/moss_transcribe_diarize.md)
## Docker 배포
기본 이미지는 CPU 모드로 시작하므로 재현 가능한 smoke test로 사용할 수 있습니다.
저장소 루트에서 다음 명령을 실행하세요. 호스트 서버가 이미 실행 중이면 중지해 포트를 비워 두세요. 기본 이미지는 예제 `server.py`를 CPU 모드로 시작하며 패키지의 `funasr-server`를 실행하지 않습니다.
Dockerfile은 버전이 고정되지 않은 PyPI FunASR와 의존 패키지를 설치하고 체크아웃의 `server.py`를 복사합니다. 앞의 소스 고정 및 editable install과 같은 환경이 아니며, 재현 가능한 모델 검증을 마친 이미지라고 볼 수도 없습니다.
```bash
cd examples/openai_api
cp .env.example .env
docker compose up --build
FUNASR_HOST_PORT=127.0.0.1:8000 docker compose up --build
```
동일한 `docker run` 명령:
이 POSIX 셸 접두 설정은 기존 Compose 파일의 호스트 공개 포트를 loopback으로 제한합니다. 컨테이너 내부 리스너는 `0.0.0.0`을 유지하세요. 컨테이너 리스너를 `127.0.0.1`로 바꾸는 것과는 다릅니다. 호스트 loopback은 로컬 개발 설정이지 인증이나 전체 네트워크의 안전성을 보장하는 기능이 아닙니다.
Compose와 동시에 실행하지 않을 때 사용할 `docker run` 명령:
```bash
docker build -t funasr-api .
docker run --rm -p 8000:8000 \
docker run --rm -p 127.0.0.1:8000:8000 \
-e FUNASR_DEVICE=cpu \
-e FUNASR_MODEL=sensevoice \
funasr-api
@@ -140,22 +213,24 @@ docker run --rm -p 8000:8000 \
GPU 호스트에서는 NVIDIA Container Toolkit과 CUDA 지원 PyTorch/FunASR 이미지가 필요합니다. CUDA 의존성에 맞게 이미지를 조정한 뒤 다음처럼 실행할 수 있습니다.
```bash
docker run --rm --gpus all -p 8000:8000 \
docker run --rm --gpus all -p 127.0.0.1:8000:8000 \
-e FUNASR_DEVICE=cuda \
-e FUNASR_MODEL=sensevoice \
funasr-api
```
컨테이너 검증:
컨테이너가 시작되면 같은 환경과 예제 디렉터리를 준비한 다른 터미널에서 다음 중 하나로 확인할 수 있습니다:
```bash
BASE_URL=http://localhost:8000 bash smoke_test.sh
python smoke_test.py --base-url http://localhost:8000
```
추가 build/run/smoke 절차는 [validate_docker.sh](validate_docker.sh)에 있지만, **기본적으로 호스트의 모든 인터페이스에 포트를 공개**하며 위의 loopback 설정을 상속하지 않습니다. 실행 전에 네트워크 설정을 확인하고 공유 네트워크의 로컬 검증에는 위의 명시적인 loopback 절차를 사용하세요. GPU 모드에는 CUDA 지원 이미지와 NVIDIA Container Toolkit이 필요합니다. 이 페이지의 설명은 Docker나 음성 추론의 실행 완료 증거가 아닙니다.
## Kubernetes 배포
팀에서 공유하거나 게이트웨이를 통해 노출하기 전에 [보안 및 게이트웨이 가이드](SECURITY.md)를 검토하고 TLS, 인증, 업로드 제한, 속도 제한, 로그 정책을 준비하세요.
팀에서 공유하거나 게이트웨이를 통해 노출하기 전에 [보안 및 게이트웨이 가이드](SECURITY.md)를 검토하고 TLS, 인증, 업로드 제한, 속도 제한, 타임아웃, 로그와 보관 기간 정책을 준비하세요.
영구 모델 캐시, 헬스 프로브, 프라이빗 `ClusterIP`를 갖춘 내부 클러스터 서비스가 필요하다면 [Kubernetes 배포 템플릿](kubernetes/README.md)에서 시작하세요. 예제 이미지를 빌드하고 push한 뒤 manifests를 적용하고, `kubectl port-forward`와 `python smoke_test.py --base-url http://localhost:8000`으로 검증합니다.
@@ -163,6 +238,8 @@ CUDA 지원 이미지와 GPU 스케줄링 설정이 준비되기 전까지는
## 설정
다음은 예제 `server.py`의 기본값이며 `funasr-server`의 기본값이 아닙니다. [API 계약](#api-contract)과 비교하세요. 로컬 절차에서는 안전을 위해 `--host 127.0.0.1`과 `--device cpu`를 명시합니다.
| 인자 | 기본값 | 설명 |
|---|---|---|
| `--host` | `0.0.0.0` | 바인드 주소 |
@@ -177,6 +254,7 @@ Docker 환경 변수:
| `FUNASR_PORT` | `8000` | `server.py`로 전달되는 컨테이너 포트 |
| `FUNASR_DEVICE` | `cpu` | 컨테이너 디바이스 모드. CUDA 지원 의존성이 포함된 이미지에서만 `cuda`로 설정하세요 |
| `FUNASR_MODEL` | `sensevoice` | 컨테이너 시작 시 로드할 모델 별칭 |
| `FUNASR_HOST_PORT` | `8000` | Compose의 호스트 포트 지정. 위 로컬 절차에서는 `127.0.0.1:8000`을 사용합니다. |
## 문제 해결
@@ -185,5 +263,5 @@ Docker 환경 변수:
| CUDA를 사용할 수 없음 | 먼저 `--device cpu`로 smoke test를 통과시키세요. |
| 8000 포트가 사용 중 | `--port 9000`으로 바꾸고 `BASE_URL=http://localhost:9000 bash smoke_test.sh` 또는 `python smoke_test.py --base-url http://localhost:9000`을 실행하세요. |
| 모델 다운로드가 느림 | 안정적인 네트워크에서 다시 시도하거나 ModelScope/Hugging Face에서 모델을 미리 다운로드하세요. |
| Dify/n8n 컨테이너에서 `localhost` 접속 실패 | 워크플로 런타임에서 접근 가능한 호스트명, Compose service name 또는 Kubernetes service name을 사용하세요. |
| 응답에 `segments`가 없음 | `response_format=verbose_json`를 설정하세요. |
| Dify/n8n 컨테이너에서 `localhost` 접속 실패 | 허가된 호스트명, Compose service name 또는 Kubernetes service name을 사용하고 접근 제어를 유지하세요. |
| 응답에 `segments`가 없음 | `response_format=verbose_json`으로 구간 필드가 있는 형식을 선택할 수 있지만 배열은 비어 있을 수 있습니다. 타임스탬프나 화자 분리를 켜는 지정이 아닙니다. [API 계약](#api-contract)을 확인하세요. |
+47 -32
View File
@@ -6,36 +6,47 @@ FunASR OpenAI 兼容 API 提供 `/v1/audio/transcriptions`,可作为私有语
本页启动仓库中的[示例服务](server.py)。打包的 `funasr-server` 是[另一套实现](../../funasr/bin/_server_app.py),复用配置前请先看[接口边界](#api-contract)。如果需要进程内 `AutoModel.generate()` 而非 HTTP 请求,请查看 [Python SDK 指南](../../docs/python_api_zh.md)([English](../../docs/python_api.md))。
打包服务的维护入口见 [Agent 接入指南](../../docs/agent_integration_zh.md)。示例服务**没有内置鉴权或上传大小限制**,`api_key="not-needed"` 也不提供鉴权。本地测试应绑定回环地址;共享前需配置 TLS、网关鉴权、上传/超时/速率限制、音频及转写保留策略,并限制健康检查、模型列表和 schema 的访问,参见[安全指南](SECURITY_zh.md)。
## API Contract
| 入口 | 启动预加载模型 | 省略 multipart `model` 时 | 说话人开关 |
|---|---|---|---|
| 在本目录运行 `python server.py` | `sensevoice` | `sensevoice` | 没有 `spk` 表单字段,只保留模型本身已经返回的说话人标签。 |
| `funasr-server` | `--model auto`:设备字符串以 `cuda` 开头时选 `fun-asr-nano`,否则选 `sensevoice` | `fun-asr-nano` | `spk=true` 为非原生说话人模型请求单独的说话人处理,默认 `False`。 |
- **在本目录运行示例 `python server.py`:**启动预加载与省略 multipart `model` 时的默认值均为 `sensevoice`。没有 `spk` 表单字段,只保留模型本身已经返回的说话人标签。
- **打包服务 `funasr-server`:**启动 `--model auto` 在设备字符串以 `cuda` 开头时选 `fun-asr-nano`,否则选 `sensevoice`;省略 multipart `model` 时仍独立默认选 `fun-asr-nano`。`spk=true` 为非原生说话人模型请求单独的说话人处理,默认 `False`。
每次请求都应明确指定 `model`:启动预加载与请求默认值是不同设置。请查询实际服务的 `/v1/models`;例如 `paraformer-en` 在示例服务中注册,却不是打包服务的内置别名。表单字段应以运行中服务的 `/openapi.json` 核对,不能只依赖仓库中的[示例规范](OPENAPI_zh.md)。
`response_format=verbose_json` 只选择响应格式,**不会启用说话人分离,也不会强制生成时间戳**。此示例仅在模型返回 `sentence_info` 时将其转换为 `segments`,否则返回 `segments=[]`。说话人标签可能缺失或为 null。MOSS 原生输出匿名标签,不需要 `spk=true` 或外部 VAD/CAM++。
SDK 的 `timestamp` 或 Nano 的 `timestamps` / `ctc_timestamps` 输出不会自动转换为 HTTP 片段。此示例接收 multipart `file`、`model`、`language`、`response_format`;SDK 的 `use_itn`、热词、原始数组及 `spk` 不是其表单字段。示例返回的 `language` 是请求提示或 `auto`,并非检测结果;打包服务可使用后端语言检测。
此示例的 `duration` 是 `generate()` 调用的耗时,单位为秒,不包含初次模型加载,**不是音频时长**。打包服务的 verbose 响应使用秒单位的音频时长,其 fallback 在无法读取音频元数据时可能使用 0。两套服务的片段 `start`/`end` 均为秒。打包服务的 fallback 可能根据文本与音频时长生成粗粒度片段,并非逐词强制对齐。打包服务的 verbose 结构包含 `task` 及片段 `id`/`words`,示例服务则包含 `model`,不能假设 JSON 字段完全相同。参见[响应示例与说话人请求](CLIENTS.md#api-contract)。
## 快速开始
从仓库根目录执行:
准备 Python 3.11,在 POSIX shell 中创建新的源码目录:
```bash
pip install funasr fastapi uvicorn python-multipart
git clone https://github.com/modelscope/FunASR.git FunASR-api
cd FunASR-api
git checkout --detach e19029adca384a06a2f60bd8c18cb98f1a0499aa
python3.11 -m venv .venv
source .venv/bin/activate
python -m pip install -e .
python -m pip install fastapi uvicorn python-multipart
python -m pip check
cd examples/openai_api
python server.py --model sensevoice --device cuda --port 8000
python server.py --host 127.0.0.1 --model sensevoice --device cpu --port 8000
```
模型加载后再检查 `GET /health`;下载及启动耗时取决于 checkpoint、缓存、网络和硬件。除非另有说明,下文命令均在本目录执行。
这里仅固定源码,未固定依赖、模型权重、音频解码器或 CUDA;只安装 PyPI 包不会得到仓库示例。这些是安装操作说明,不代表已在你的硬件上验证全新安装或声学推理。
模型加载后再检查 `GET /health`;下载及启动耗时取决于 checkpoint、缓存、网络和硬件。健康检查成功不等于转写正确。除非另有说明,下文命令均在本目录执行。准备好 CUDA 依赖后,可将 CPU 启动命令替换为 `python server.py --host 127.0.0.1 --model sensevoice --device cuda --port 8000`,不要同时在相同端口启动两次。
需要直接复制的接入示例?可以继续查看 [客户端配方](CLIENTS.md)、[JavaScript/TypeScript 配方](JAVASCRIPT_zh.md)、[Gradio 浏览器 Demo](GRADIO_zh.md)、[工作流配方](WORKFLOWS_zh.md)、[Postman 集合](POSTMAN_zh.md)、[OpenAPI 规范](OPENAPI_zh.md)、[安全与网关指南](SECURITY_zh.md) 和 [Kubernetes 部署模板](kubernetes/README_zh.md)。
### 端到端 smoke test
在另一个终端运行:
在另一个终端进入同一源码目录、激活 `.venv`,再进入 `examples/openai_api`。以下可选脚本检查健康状态与转写:
```bash
bash smoke_test.sh
@@ -43,11 +54,13 @@ bash smoke_test.sh
python smoke_test.py
```
等价手动命令:
等价手动命令使用公开的中文示例音频,不是日语或韩语验证集:
```bash
curl -L https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/BAC009S0764W0121.wav -o sample.wav
curl http://localhost:8000/health
curl http://localhost:8000/v1/models
curl http://localhost:8000/openapi.json
curl http://localhost:8000/v1/audio/transcriptions \
-F file=@sample.wav \
-F model=sensevoice \
@@ -59,7 +72,7 @@ curl http://localhost:8000/v1/audio/transcriptions \
如果希望用本地浏览器上传音频或测试麦克风,先启动 API 服务,再运行可选 Gradio 前端:
```bash
pip install gradio
python -m pip install gradio
python gradio_app.py --base-url http://localhost:8000
```
@@ -67,24 +80,21 @@ python gradio_app.py --base-url http://localhost:8000
## 使用 OpenAI SDK
先运行 `pip install openai` 安装独立的 HTTP 客户端;它不是 FunASR Python SDK。
在同一已激活环境运行 `python -m pip install openai` 安装独立的 HTTP 客户端;它不是 FunASR Python SDK。将 `meeting.wav` 替换为解码环境支持的真实本地音频文件。
```python
from openai import OpenAI
client = OpenAI(base_url="http://localhost:8000/v1", api_key="not-needed")
result = client.audio.transcriptions.create(
model="sensevoice", # 也可以使用 "paraformer"、"paraformer-en"、"fun-asr-nano"
file=open("meeting.wav", "rb"),
)
with open("meeting.wav", "rb") as audio:
result = client.audio.transcriptions.create(model="sensevoice", file=audio)
print(result.text)
verbose = client.audio.transcriptions.create(
model="sensevoice",
file=open("meeting.wav", "rb"),
response_format="verbose_json",
)
with open("meeting.wav", "rb") as audio:
verbose = client.audio.transcriptions.create(
model="sensevoice", file=audio, response_format="verbose_json",
)
# verbose_json does not enable diarization; segments may be empty
print(getattr(verbose, "segments", []))
```
@@ -106,19 +116,20 @@ curl http://localhost:8000/v1/audio/transcriptions \
下列别名来自此示例的 `MODEL_CONFIGS`,不是整个 SDK 或所有服务统一支持的模型列表。接口会去除返回文本中的 `<|...|>` 富文本标签,不会输出独立的情感/事件字段。
| Model | 示例配置 | 响应限制 |
|---|---|---|
| `sensevoice` | SenseVoiceSmall + FSMN-VAD | 默认不启用句子时间戳或外部说话人聚类。 |
| `paraformer` | `paraformer-zh` + FSMN-VAD + CT 标点 | 已配置标点;仅设置 `verbose_json` 不会请求句子记录。 |
| `paraformer-en` | `paraformer-en` + FSMN-VAD | 相对于打包服务,这是示例专有别名;此处没有配置标点组件。 |
| `fun-asr-nano` | `AutoModel` 加载 Fun-ASR-Nano,HF 平台 + FSMN-VAD | 此示例不使用 vLLM;CTC 时间戳依赖完整 checkpoint 权重。 |
| `moss-transcribe-diarize` | 第三方 OpenMOSS 原生转写/说话人适配器 | 需要独立依赖环境;保留模型返回的时间戳与匿名标签。 |
- `sensevoice`:SenseVoiceSmall + FSMN-VAD。默认不启用句子时间戳或外部说话人聚类。
- `paraformer`:`paraformer-zh` + FSMN-VAD + CT 标点。已配置标点;仅设置 `verbose_json` 不会请求句子记录。
- `paraformer-en`:`paraformer-en` + FSMN-VAD。相对于打包服务,这是示例专有别名;此处没有配置标点组件。
- `fun-asr-nano`:`AutoModel` 加载 Fun-ASR-Nano,HF 平台 + FSMN-VAD。此示例不使用 vLLM;CTC 时间戳依赖完整 checkpoint 权重。
- `moss-transcribe-diarize`:第三方 OpenMOSS 原生转写/说话人适配器。需要独立依赖环境;保留模型返回的时间戳与匿名标签。
具体 checkpoint 的语言与许可证信息请查看[模型选择](../../docs/model_selection_zh.md) 和模型自身许可证。FunASR 软件的 MIT 许可证不代表所有模型权重的许可证。请在实际工作负载上测量所选路线,不应把别名当作统一的速度或容量规格。
Fun-ASR-MLT-Nano 是独立的多语种 checkpoint,不是这两套服务的内置别名;基础 Nano 不能据此宣称支持韩语。自定义 checkpoint 应走打包服务的 `--model-path`、`--hub` 与请求 `model="custom"`,这些不是示例服务的选项。具体环境与权重配置见模型选择和 Agent 指南。
MOSS 使用固定 revision 的第三方 HF 模型,不能外挂 VAD 或说话人模型。完整的
`funasr-server`、Docker Compose、Kubernetes、vLLM、SGLang Omni、LocalAI 与
FunClip 路径见 [MOSS 部署指南](../../docs/moss_transcribe_diarize_zh.md)。
说话人标签仅表示单段录音内的匿名说话人,不代表真实身份或跨录音识别。别名出现在 `/v1/models` 中,也不代表其依赖及权重已经就绪。
## API 端点
@@ -135,6 +146,8 @@ FunClip 路径见 [MOSS 部署指南](../../docs/moss_transcribe_diarize_zh.md)
可以通过 multipart HTTP 或工具函数模式接入 **LangChain**、**LlamaIndex**、**AutoGen**、**CrewAI**、**Semantic Kernel**、**Dify**、**n8n**,但应按本服务支持的字段验证具体集成。这些示例不保证兼容每个框架版本或实时 API。
两套服务均将工作流请求别名 `whisper-1` 映射到启动选择的模型,不会因此运行 OpenAI Whisper。工作流容器内的 `localhost` 指向该容器;应配置经过授权、可到达的网关或服务地址,不要用无保护的公网暴露绕过网络问题。
- SDK、JavaScript/TypeScript 和 Agent tool 写法见 [客户端配方](CLIENTS.md) 与 [JavaScript/TypeScript 配方](JAVASCRIPT_zh.md)。
- Dify、n8n、HTTP 节点和 webhook worker 见 [工作流配方](WORKFLOWS_zh.md)。
- 图形界面 smoke test 见 [Postman 集合](POSTMAN_zh.md)。
@@ -144,11 +157,13 @@ FunClip 路径见 [MOSS 部署指南](../../docs/moss_transcribe_diarize_zh.md)
从仓库根目录执行下列命令。默认镜像以 CPU 模式启动示例 `server.py`,不是打包的 `funasr-server`。
以下仅是本地开发的端口发布设置,不提供鉴权。容器内仍监听 `0.0.0.0`,宿主机发布端口才绑定 `127.0.0.1`,不能将容器监听改为回环地址。当前 Dockerfile 安装未锁版本的 PyPI FunASR/依赖并复制此示例,不能视为上方固定源码的 Python 环境或可复现的声学环境。
```bash
cd examples/openai_api
cp .env.example .env
docker compose up --build
FUNASR_HOST_PORT=127.0.0.1:8000 docker compose up --build
```
等价 `docker run`:
@@ -156,7 +171,7 @@ docker compose up --build
```bash
docker build -t funasr-api .
docker run --rm -p 8000:8000 \
docker run --rm -p 127.0.0.1:8000:8000 \
-e FUNASR_DEVICE=cpu \
-e FUNASR_MODEL=sensevoice \
funasr-api
@@ -165,7 +180,7 @@ docker run --rm -p 8000:8000 \
GPU 环境需要 NVIDIA Container Toolkit 和 CUDA-capable PyTorch/FunASR 镜像。适配 CUDA 依赖后,可使用:
```bash
docker run --rm --gpus all -p 8000:8000 \
docker run --rm --gpus all -p 127.0.0.1:8000:8000 \
-e FUNASR_DEVICE=cuda \
-e FUNASR_MODEL=sensevoice \
funasr-api
@@ -178,7 +193,7 @@ BASE_URL=http://localhost:8000 bash smoke_test.sh
python smoke_test.py --base-url http://localhost:8000
```
如果想用一个命令完成 build、run 和 smoke test,可以用 `bash validate_docker.sh` 验证便携 CPU 镜像。具备 NVIDIA Container Toolkit 且镜像已适配 CUDA 时,可以用 `bash validate_docker.sh --gpu` 以 `FUNASR_DEVICE=cuda` 跑同一套 smoke test。
可选的 [validate_docker.sh](validate_docker.sh) 整合了 build/run/smoke,但**默认向所有宿主机接口发布端口**,不会继承上方的回环地址设置。执行前须检查其网络配置;在共享网络做本地测试时,请使用上方明确绑定回环地址的 build/run/smoke 配方。脚本的 GPU 模式还需要 NVIDIA Container Toolkit 和 CUDA-capable 镜像。这些说明不代表已完成 Docker 或声学推理测试。
## Kubernetes 部署
+144 -2
View File
@@ -12,9 +12,28 @@ from urllib.parse import unquote, urlsplit
ROOT = Path(__file__).resolve().parents[1]
EXAMPLE = ROOT / "examples/openai_api"
DOCS = [EXAMPLE / name for name in ("CLIENTS.md", "README.md", "README_zh.md")]
READMES = [EXAMPLE / name for name in ("README.md", "README_zh.md", "README_ja.md", "README_ko.md")]
DOCS = [EXAMPLE / "CLIENTS.md", *READMES]
PACKAGED = ROOT / "funasr/bin/_server_app.py"
# Frozen from the pre-edit README inventory, excluding fenced code comments.
LEGACY_HEADINGS = {
"README.md": "FunASR OpenAI-Compatible API Server|API Contract|Quick Start|End-to-end smoke test|Browser demo with Gradio|Usage with OpenAI SDK (Python)|Usage with curl|Available Models|API Endpoints|Agent Framework Integration|LangChain Example|Docker Deployment|Kubernetes Deployment|Configuration|Troubleshooting",
"README_zh.md": "FunASR OpenAI 兼容 API 服务|API Contract|快速开始|端到端 smoke test|Gradio 浏览器 Demo|使用 OpenAI SDK|使用 curl|可用模型|API 端点|Agent 与低代码工作流|Docker 部署|Kubernetes 部署|配置|故障排查",
"README_ja.md": "FunASR OpenAI 互換 API サーバー|クイックスタート|エンドツーエンド smoke test|Gradio ブラウザデモ|OpenAI SDK で使う|curl で使う|利用できるモデル|API エンドポイント|エージェントとローコードワークフロー|Docker デプロイ|Kubernetes デプロイ|設定|トラブルシューティング",
"README_ko.md": "FunASR OpenAI 호환 API 서버|빠른 시작|엔드투엔드 smoke test|Gradio 브라우저 데모|OpenAI SDK로 사용하기|curl로 사용하기|사용 가능한 모델|API 엔드포인트|에이전트 및 로우코드 워크플로|Docker 배포|Kubernetes 배포|설정|문제 해결",
}
def supported_module_command(args):
"""Allow only the documented environment setup commands, not arbitrary modules."""
return args in (["python3.11", "-m", "venv", ".venv"],
["python", "-m", "pip", "install", "-e", "."],
["python", "-m", "pip", "install", "fastapi", "uvicorn", "python-multipart"],
["python", "-m", "pip", "install", "openai"],
["python", "-m", "pip", "install", "gradio"],
["python", "-m", "pip", "check"])
def blocks(text, language):
return re.findall(r"^```" + language + r"\n(.*?)^```", text, re.M | re.S)
@@ -52,6 +71,85 @@ def headings(text):
class ServiceDocsContract(unittest.TestCase):
def test_preserves_existing_readme_headings(self):
for path in READMES:
text = path.read_text(encoding="utf-8")
source = re.sub(r"^```[^\n]*\n.*?^```[^\n]*$", "", text, flags=re.M | re.S)
actual = re.findall(r"^#{1,6}\s+(.+?)\s*#*\s*$", source, re.M)
expected = LEGACY_HEADINGS[path.name].split("|")
self.assertEqual([value for value in actual if value in expected], expected, path.name)
def test_long_contract_and_model_explanations_are_readable_lists(self):
model_headings = ("Available Models", "可用模型", "利用できるモデル", "사용 가능한 모델")
configs = next(node for node in ast.walk(tree(EXAMPLE / "server.py"))
if isinstance(node, ast.Assign) and any(isinstance(target, ast.Name)
and target.id == "MODEL_CONFIGS" for target in node.targets))
aliases = [ast.literal_eval(key) for key in configs.value.keys]
for path, model_heading in zip(READMES, model_headings):
text = path.read_text(encoding="utf-8")
for heading in ("API Contract", model_heading):
section = text.split("## " + heading + "\n", 1)[1].split("\n## ", 1)[0]
self.assertNotRegex(section, r"(?m)^\|", path.name)
if heading == model_heading:
self.assertEqual(re.findall(r"(?m)^- `([^`]+)`", section), aliases, path.name)
def test_module_command_allowlist_rejects_unchecked_variants(self):
self.assertTrue(supported_module_command(["python", "-m", "pip", "check"]))
for args in (["python", "-m", "missing"], ["python", "-m", "pip", "uninstall", "funasr"],
["python3.11", "-m", "venv"], ["python", "-m", "pip", "check", "--bogus"]):
self.assertFalse(supported_module_command(args))
def test_readme_checkout_and_loopback_recipes(self):
for path in READMES:
text = path.read_text(encoding="utf-8")
quick_start = blocks(text, "bash")[0]
ordered = ["git clone https://github.com/modelscope/FunASR.git FunASR-api", "cd FunASR-api",
"git checkout --detach e19029adca384a06a2f60bd8c18cb98f1a0499aa",
"python3.11 -m venv .venv", "source .venv/bin/activate",
"python -m pip install -e .", "python -m pip install fastapi uvicorn python-multipart",
"python -m pip check", "cd examples/openai_api",
"python server.py --host 127.0.0.1 --model sensevoice --device cpu --port 8000"]
positions = [quick_start.index(command) for command in ordered]
self.assertEqual(positions, sorted(positions), path.name)
self.assertIn("SECURITY", text[:text.index("```bash")])
for marker in ("/v1/models", "/openapi.json", "Fun-ASR-MLT-Nano", '--model-path', '--hub',
'model="custom"', 'whisper-1', 'sentence_info', 'ctc_timestamps', '0.0.0.0'):
self.assertIn(marker, text, path.name)
self.assertIn("FUNASR_HOST_PORT=127.0.0.1:8000 docker compose up --build", text)
for code in blocks(text, "bash"):
for line in code.replace("\\\n", " ").splitlines():
args = shlex.split(line, comments=True)
if args[:2] == ["docker", "run"]:
self.assertEqual(args[args.index("-p") + 1], "127.0.0.1:8000:8000")
self.assertNotRegex(text, r"(?:170|120|3\.6)[x×倍]")
def test_readme_clients_close_files_and_use_supported_form_fields(self):
allowed = set(form_defaults(function(EXAMPLE / "server.py", "transcribe"))) | {"file"}
for path in READMES:
formats = []
for code in blocks(path.read_text(encoding="utf-8"), "python"):
parsed = ast.parse(code)
calls = [node for node in ast.walk(parsed) if isinstance(node, ast.Call)
and isinstance(node.func, ast.Attribute) and node.func.attr == "create"]
for call in calls:
kwargs = {kw.arg: kw.value for kw in call.keywords}
formats.append(ast.literal_eval(kwargs["response_format"]) if "response_format" in kwargs else None)
self.assertEqual(ast.literal_eval(kwargs["model"]), "sensevoice")
self.assertLessEqual(set(kwargs), allowed)
self.assertIsInstance(kwargs["file"], ast.Name)
contexts = [node for node in ast.walk(parsed) if isinstance(node, ast.With)
and call in list(ast.walk(node))]
self.assertTrue(any(isinstance(item.optional_vars, ast.Name)
and item.optional_vars.id == kwargs["file"].id
and isinstance(item.context_expr, ast.Call)
and isinstance(item.context_expr.func, ast.Name)
and item.context_expr.func.id == "open"
and len(item.context_expr.args) == 2
and ast.literal_eval(item.context_expr.args[1]) == "rb"
for node in contexts for item in node.items))
self.assertGreaterEqual(len(formats), 2, path.name)
self.assertEqual(set(formats), {None, "verbose_json"}, path.name)
def test_relative_links_and_owned_page_anchors(self):
owned = {path.resolve() for path in DOCS}
for path in DOCS:
@@ -101,7 +199,10 @@ class ServiceDocsContract(unittest.TestCase):
for code in blocks(text, "bash"):
for line in code.replace("\\\n", " ").splitlines():
args = shlex.split(line, comments=True)
if len(args) < 2 or args[0] not in ("python", "bash"):
if len(args) < 2 or args[0] not in ("python", "python3.11", "bash"):
continue
if args[1] == "-m":
self.assertTrue(supported_module_command(args), args)
continue
script = EXAMPLE / args[1]
self.assertTrue(script.is_file(), str(script))
@@ -130,6 +231,47 @@ class ServiceDocsContract(unittest.TestCase):
aliases = [ast.literal_eval(key) for key in assignment.value.keys]
self.assertEqual("paraformer-en" in aliases, expected)
def test_example_serializes_sentence_info_not_raw_sdk_timestamps(self):
handler = function(EXAMPLE / "server.py", "transcribe")
branch = next(node for node in ast.walk(handler) if isinstance(node, ast.If)
and isinstance(node.test, ast.Compare)
and isinstance(node.test.left, ast.Name)
and node.test.left.id == "response_format")
class CaptureReturn(ast.NodeTransformer):
def visit_Return(self, node):
return ast.copy_location(ast.Assign(
targets=[ast.Name(id="response", ctx=ast.Store())], value=node.value), node)
executable = ast.fix_missing_locations(CaptureReturn().visit(ast.Module(body=[branch], type_ignores=[])))
clean = function(EXAMPLE / "server.py", "clean_text")
namespace = {"re": re, "JSONResponse": lambda value: value, "response_format": "verbose_json",
"elapsed": 0.42, "language": None, "model": "sensevoice", "text": "hello"}
exec(compile(ast.Module(body=[clean], type_ignores=[]), "clean-text", "exec"), namespace)
raw_token = {"token": "hello", "start_time": 100, "end_time": 400}
cases = [({"timestamp": [[100, 400]], "timestamps": [raw_token], "ctc_timestamps": [raw_token]}, []),
({"sentence_info": [{"start": 100, "end": 400, "text": "<|HAPPY|>hello"}]},
[{"start": 0.1, "end": 0.4, "text": "hello", "speaker": None}])]
for result, expected in cases:
namespace["result"] = [result]
exec(compile(executable, "example-verbose-branch", "exec"), namespace)
self.assertEqual(namespace["response"]["segments"], expected)
self.assertEqual(namespace["response"]["language"], "auto")
self.assertEqual(namespace["response"]["duration"], 0.42)
def test_workflow_alias_and_compose_host_binding_match_source(self):
resolver = function(EXAMPLE / "server.py", "resolve_openai_transcription_model")
namespace = {"N8N_OPENAI_MODEL_ALIAS": "whisper-1", "DEFAULT_MODEL": "sensevoice"}
exec(compile(ast.Module(body=[resolver], type_ignores=[]), "workflow-alias", "exec"), namespace)
self.assertEqual(namespace[resolver.name]("whisper-1"), "sensevoice")
self.assertEqual(namespace[resolver.name]("paraformer"), "paraformer")
compose = (EXAMPLE / "docker-compose.yml").read_text(encoding="utf-8")
self.assertIn('"${FUNASR_HOST_PORT:-8000}:8000"', compose)
dockerfile = (EXAMPLE / "Dockerfile").read_text(encoding="utf-8")
command = next(json.loads(line[4:]) for line in dockerfile.splitlines() if line.startswith("CMD "))
args = shlex.split(command[-1])
self.assertEqual(args[args.index("--host") + 1], "0.0.0.0")
def test_startup_defaults_match_source(self):
for path, expected in ((EXAMPLE / "server.py", "sensevoice"),
(ROOT / "funasr/bin/server.py", "auto")):