mirror of
https://github.com/modelscope/FunASR.git
synced 2026-10-12 02:19:18 +00:00
docs: clarify low-code workflow deployment and response contracts
Signed-off-by: zhifu gao <[email protected]>
This commit is contained in:
@@ -2,65 +2,89 @@
|
||||
|
||||
[中文](WORKFLOWS_zh.md)
|
||||
|
||||
Use this guide when you want Dify, n8n, webhook workers, or another workflow engine to call a private FunASR speech API. Start with the local smoke test in this directory, then replace `localhost` with the reachable service name inside your network.
|
||||
Use this guide when you want Dify, n8n, webhook workers, or another workflow engine to call a private FunASR speech API. These are multipart HTTP recipes, not a compatibility guarantee for every workflow product or version.
|
||||
|
||||
The example server has **no built-in authentication or upload limit**. A client URL containing `localhost` does not restrict the server's listening address. Keep local checks on loopback; before sharing, configure TLS, gateway authentication, upload/time/rate limits, private health/model/schema access, and audio/transcript retention using the [security and gateway guide](SECURITY.md). A placeholder API key does not authenticate the server.
|
||||
|
||||
## Server preflight
|
||||
|
||||
First prepare the checkout and environment in the [example README](README.md#quick-start). Its fixed source revision is not a dependency/model lock or proof of a clean installation. The commands below start in that checkout's root, with its `.venv` already prepared. If the server is already running, skip startup and proceed to the checks; do not start a second process on the same port.
|
||||
|
||||
```bash
|
||||
cd examples/openai_api
|
||||
python server.py --model sensevoice --device cuda --port 8000
|
||||
source ../../.venv/bin/activate
|
||||
python server.py --host 127.0.0.1 --model sensevoice --device cpu --port 8000
|
||||
```
|
||||
|
||||
From the workflow host or container:
|
||||
After preparing CUDA dependencies, replace the CPU command with `python server.py --host 127.0.0.1 --model sensevoice --device cuda --port 8000`. For the distinct packaged `funasr-server` route, use [Agent integration](../../docs/agent_integration.md); startup defaults, aliases and response fields differ from this example.
|
||||
|
||||
In a second terminal on the same host, enter the same checkout's `examples/openai_api` directory and activate the same environment. Wait for model loading, then check the local service:
|
||||
|
||||
```bash
|
||||
export FUNASR_BASE_URL=http://<funasr-host>:8000
|
||||
source ../../.venv/bin/activate
|
||||
export FUNASR_BASE_URL="http://127.0.0.1:8000"
|
||||
curl -fsS "$FUNASR_BASE_URL/health"
|
||||
curl -fsS "$FUNASR_BASE_URL/v1/models"
|
||||
curl -fsS "$FUNASR_BASE_URL/openapi.json"
|
||||
```
|
||||
|
||||
If the workflow engine runs in Docker, `localhost` usually means the workflow container itself. Use a Docker Compose service name, Kubernetes service name, or LAN host name instead.
|
||||
Health and schema checks do not establish acoustic correctness. For transcription, use an existing local audio file as `meeting.wav` below; the README's public Chinese smoke sample is not a multilingual accuracy benchmark.
|
||||
|
||||
If the workflow engine runs in Docker, `localhost` usually means the workflow container itself. A host-loopback server is not automatically reachable from that container. Deliberately configure a private gateway/container network, then replace `FUNASR_BASE_URL` and the worker's `FUNASR_URL` with the address reachable from the workflow runtime. Use real gateway credentials when required; the local curl/Python examples below do not add authentication headers. Do not solve connectivity by exposing an unauthenticated port publicly.
|
||||
|
||||
## Postman smoke test
|
||||
|
||||
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.
|
||||
For offline multi-speaker meetings, first prepare the isolated third-party MOSS service in the [MOSS deployment guide](../../docs/moss_transcribe_diarize.md), including its GPU and file-duration limits. Then set `MODEL_ALIAS=moss-transcribe-diarize` and keep `response_format=verbose_json` to preserve available native anonymous speaker segments. Do not add external VAD or `spk=true`; recording-local labels are not verified identities or stable IDs across recordings. Changing a client alias alone does not prepare that service.
|
||||
|
||||
## Multipart HTTP request
|
||||
|
||||
Every workflow engine eventually needs to send this request shape:
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| Method | `POST` |
|
||||
| URL | `http://<funasr-host>:8000/v1/audio/transcriptions` |
|
||||
| Body type | `multipart/form-data` |
|
||||
| File field | `file` |
|
||||
| Text field | `model=sensevoice` |
|
||||
| Text field | `response_format=verbose_json` |
|
||||
| Timeout | Set according to maximum audio duration, for example 300 seconds for long files. |
|
||||
- **Method:** `POST`
|
||||
- **URL:** `http://<funasr-host>:8000/v1/audio/transcriptions`
|
||||
- **Body type:** `multipart/form-data`
|
||||
- **File field:** `file`
|
||||
- **Text field:** `model=sensevoice`
|
||||
- **Text field:** `response_format=verbose_json`
|
||||
- **Timeout:** Set according to maximum audio duration, for example 300 seconds for long files.
|
||||
|
||||
Equivalent curl command:
|
||||
|
||||
```bash
|
||||
curl "$FUNASR_BASE_URL/v1/audio/transcriptions" \
|
||||
curl -fsS "$FUNASR_BASE_URL/v1/audio/transcriptions" \
|
||||
-F file=@meeting.wav \
|
||||
-F model=sensevoice \
|
||||
-F response_format=verbose_json
|
||||
```
|
||||
|
||||
Typical JSON fields to map downstream:
|
||||
Map `text` to the transcript. `response_format=verbose_json` selects a format: it does not enable diarization or force timestamps. Check the [client response contract](CLIENTS.md#response-formats) before consuming the other fields:
|
||||
|
||||
| Path | Use |
|
||||
|---|---|
|
||||
| `text` | Plain transcript for a chatbot, ticket, or knowledge-base step. |
|
||||
| `segments` | Timestamps and speaker labels when `verbose_json` is requested. |
|
||||
| `duration` | Audio processing time reported by the API, useful for logs. |
|
||||
| `model` | Model alias used for the request. |
|
||||
- **Example `server.py`:** `segments` comes only from model-provided `sentence_info`, otherwise `segments=[]`. `duration` is elapsed seconds around `generate()`, excluding initial model loading, not recording length. `model` is the resolved request alias; `language` echoes the submitted hint or `auto`, not detected language.
|
||||
- **Packaged `funasr-server`:** `duration` is audio length in seconds, with 0 possible if fallback audio metadata cannot be read. It can return coarse text-based segments, not forced alignment. Its verbose response includes `task` and segment `id`/`words`, but no top-level `model`; language can use backend detection. For non-native diarization models, `spk=true` opts into a separate speaker pipeline, subject to its model/dependencies. The example has no `spk` form field.
|
||||
|
||||
Segment `start`/`end` use seconds, not the SDK's millisecond coordinates. A `speaker` field can be absent, null, numeric or a string; labels do not identify a person. Neither nonempty segments nor `verbose_json` guarantees accurate subtitle alignment. HTTP display text strips SenseVoice rich tags; it is not the SDK raw-tag result or a dedicated emotion/event response. SDK fields/options such as `timestamp`, `timestamps`, `ctc_timestamps`, `use_itn`, hotwords and raw arrays are not additional form fields; raw SDK timestamps are not automatically converted into HTTP segments.
|
||||
|
||||
Illustrative example-server response with no `sentence_info`; `0.42` is processing time, not audio duration:
|
||||
|
||||
```json
|
||||
{"text": "recognized speech", "segments": [], "language": "auto", "duration": 0.42, "model": "sensevoice"}
|
||||
```
|
||||
|
||||
Illustrative packaged-server response for a 3.2-second file and a coarse fallback segment, without speaker opt-in. These examples describe schemas, not new model measurements:
|
||||
|
||||
```json
|
||||
{
|
||||
"task": "transcribe",
|
||||
"language": "en",
|
||||
"duration": 3.2,
|
||||
"text": "recognized speech",
|
||||
"segments": [
|
||||
{"id": 0, "start": 0.0, "end": 3.2, "text": "recognized speech", "words": []}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Dify custom tool or HTTP node
|
||||
|
||||
@@ -75,21 +99,33 @@ Configure an HTTP request node or custom tool with:
|
||||
- Body: `multipart/form-data`
|
||||
- File part: `file`, bound to the uploaded audio variable
|
||||
- Text parts: `model=sensevoice`, `response_format=verbose_json`
|
||||
- Output variable: map `text` as the transcript, and keep `segments` when timestamps or speaker labels matter
|
||||
- Output variable: map `text` as the transcript; inspect `segments` availability and provenance before using timestamps or speaker labels
|
||||
|
||||
### Audio URL path
|
||||
|
||||
Some workflow tools pass a file URL rather than raw multipart bytes. In that case, add a small internal worker:
|
||||
Some workflow tools pass a file URL rather than raw multipart bytes. A URL string in the multipart `file` field is not an audio upload. Prefer direct binary uploads or controlled storage object IDs resolved by a reviewed storage client.
|
||||
|
||||
1. Dify sends the audio URL and metadata to the worker.
|
||||
The following sketch is only for an operator-approved URL in trusted storage. Destination allowlists, private-network access policy, redirect validation, download byte limits and authentication are **not implemented**. `requests.get` follows redirects and buffers the entire response; its timeout is not a byte cap or a complete end-to-end deadline. Do not pass user-supplied URLs to this helper. Before accepting them, require a reviewed download boundary enforcing all those controls, including blocking unintended private/metadata destinations and checking each redirect. Being inside a trusted network is not an SSRF defense.
|
||||
|
||||
For this trusted-input illustration:
|
||||
|
||||
1. An operator supplies an approved audio URL and metadata to the worker.
|
||||
2. The worker downloads the file from trusted storage.
|
||||
3. The worker posts multipart data to FunASR.
|
||||
4. The worker returns `text`, `segments`, and operational logs to Dify.
|
||||
4. The worker returns the service JSON; downstream nodes check optional fields. Logs must not expose signed URLs, credentials or private transcripts.
|
||||
|
||||
In the same activated client environment, install the separate HTTP dependency:
|
||||
|
||||
```bash
|
||||
python -m pip install requests
|
||||
```
|
||||
|
||||
Set `FUNASR_URL` for the prepared service; this default is for a worker on the same host. The function definitions can be imported by your worker, but do not create an HTTP listener or implement inbound authentication/upload limits.
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
FUNASR_URL = "http://funasr-api:8000/v1/audio/transcriptions"
|
||||
FUNASR_URL = "http://127.0.0.1:8000/v1/audio/transcriptions"
|
||||
|
||||
def transcribe_from_url(audio_url: str) -> dict:
|
||||
audio_response = requests.get(audio_url, timeout=120)
|
||||
@@ -101,7 +137,7 @@ def transcribe_from_url(audio_url: str) -> dict:
|
||||
return response.json()
|
||||
```
|
||||
|
||||
Keep this worker inside your trusted network and validate allowed URL domains before downloading user-provided links.
|
||||
Keep this illustration restricted to approved inputs; hostname validation alone is not a complete safe-download policy.
|
||||
|
||||
## n8n HTTP Request node
|
||||
|
||||
@@ -109,39 +145,31 @@ A common n8n flow is: trigger -> binary audio data -> HTTP Request -> transcript
|
||||
|
||||
Recommended HTTP Request settings:
|
||||
|
||||
| n8n setting | Value |
|
||||
|---|---|
|
||||
| Method | `POST` |
|
||||
| URL | `http://<funasr-host>:8000/v1/audio/transcriptions` |
|
||||
| Send Body | enabled |
|
||||
| Body Content Type | `Form-Data` / multipart |
|
||||
| Binary file field | `file` |
|
||||
| Additional form fields | `model=sensevoice`, `response_format=verbose_json` |
|
||||
| Response Format | JSON |
|
||||
| Timeout | Increase for long recordings. |
|
||||
- **Method:** `POST`
|
||||
- **URL:** `http://<funasr-host>:8000/v1/audio/transcriptions`
|
||||
- **Send Body:** enabled
|
||||
- **Body Content Type:** `Form-Data` / multipart
|
||||
- **Binary file field:** `file`
|
||||
- **Additional form fields:** `model=sensevoice`, `response_format=verbose_json`
|
||||
- **Response Format:** JSON
|
||||
- **Timeout:** Increase for long recordings.
|
||||
|
||||
After the request, use `{{$json.text}}` as the transcript. If `verbose_json` is enabled, route `{{$json.segments}}` to subtitle, speaker, or QA steps.
|
||||
After the request, use `{{$json.text}}` as the transcript. Route `{{$json.segments}}` onward only after checking it exists and is useful for the task; empty or coarse segments must not be treated as verified subtitle timing or diarization. Node labels and binary-property configuration vary with the installed n8n version; `file` is the outgoing multipart field, not necessarily the name of the incoming binary property.
|
||||
|
||||
### n8n OpenAI Audio node
|
||||
|
||||
The built-in OpenAI node can also run its Audio > Transcribe operation against
|
||||
FunASR. In OpenAI credentials, set Base URL to
|
||||
`http://<funasr-host>:8000/v1` and provide any non-empty API key. The node
|
||||
always sends `model=whisper-1`; FunASR maps that compatibility alias to the
|
||||
model selected when the server starts. This path is transcription-only. Use the
|
||||
HTTP Request node above when you need `verbose_json`, segments, or speaker
|
||||
labels.
|
||||
For OpenAI Audio > Transcribe node versions that send `model=whisper-1`, FunASR maps that compatibility alias to the model selected at server startup; it does not select a Whisper checkpoint. Set Base URL to your reachable service URL with `/v1`. A non-empty placeholder key is only for an unprotected local endpoint; supply real credentials for an authenticated gateway. Verify the installed node version's request behavior rather than assuming all versions match. Use this recipe for plain transcription; use the HTTP Request node for explicit `response_format` and any supported speaker opt-in, still subject to the response limits above.
|
||||
|
||||
## Webhook worker pattern
|
||||
|
||||
Use this when the workflow engine cannot send multipart files reliably or when audio needs pre-processing.
|
||||
Use this when the workflow engine cannot send multipart files reliably or when audio needs pre-processing. This POSIX temporary-file example uses the same `requests` dependency and closes the upload handle. It accepts bytes already held in memory: apply upload limits before buffering them. It is a function, not a protected webhook server.
|
||||
|
||||
```python
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
import requests
|
||||
|
||||
FUNASR_URL = "http://localhost:8000/v1/audio/transcriptions"
|
||||
FUNASR_URL = "http://127.0.0.1:8000/v1/audio/transcriptions"
|
||||
|
||||
def transcribe_bytes(filename: str, payload: bytes, content_type: str = "audio/wav") -> dict:
|
||||
with tempfile.NamedTemporaryFile(suffix=Path(filename).suffix or ".wav") as tmp:
|
||||
@@ -158,7 +186,7 @@ def transcribe_bytes(filename: str, payload: bytes, content_type: str = "audio/w
|
||||
return response.json()
|
||||
```
|
||||
|
||||
This worker is also the right place to add audio conversion, file-size checks, request IDs, authentication, and retries.
|
||||
Audio conversion, file-size checks, request IDs, inbound/upstream authentication and retry policy are not implemented here. Enforce them at the appropriate boundary before shared use; retries can repeat expensive transcription work.
|
||||
|
||||
## Production guardrails
|
||||
|
||||
@@ -171,11 +199,9 @@ This worker is also the right place to add audio conversion, file-size checks, r
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Symptom | Fix |
|
||||
|---|---|
|
||||
| Workflow can call `/health` but transcription fails | Confirm the request is `multipart/form-data` and the binary field is named `file`. |
|
||||
| `localhost` connection fails from Dify or n8n | Use the host, Compose service, or Kubernetes service reachable from the workflow runtime. |
|
||||
| Response has no `segments` | Set `response_format=verbose_json`. |
|
||||
| Requests time out | Increase HTTP timeout or split long recordings. |
|
||||
| First request is slow | Preload the model with `--model sensevoice` and use `/health` as a readiness check. |
|
||||
| Unknown model alias | Call `/v1/models` and use one of the returned aliases. |
|
||||
- **Workflow can call `/health` but transcription fails:** Confirm the request is `multipart/form-data` and the binary field is named `file`.
|
||||
- **`localhost` connection fails from Dify or n8n:** Use the host, Compose service, or Kubernetes service reachable from the workflow runtime.
|
||||
- **Response has no usable `segments`:** Check the format and deployed schema, then the model's `sentence_info` and speaker configuration; `verbose_json` alone cannot create timestamps or labels.
|
||||
- **Requests time out:** Increase HTTP timeout or split long recordings.
|
||||
- **First request is slow:** Preload the model with `--model sensevoice` and use `/health` as a readiness check.
|
||||
- **Unknown model alias:** Call `/v1/models` and use one of the returned aliases.
|
||||
|
||||
@@ -2,64 +2,89 @@
|
||||
|
||||
[English](WORKFLOWS.md)
|
||||
|
||||
当你希望让 Dify、n8n、HTTP 节点、webhook worker 或其他低代码工作流引擎调用私有 FunASR 语音 API 时,可以从这份配方开始。建议先在本目录跑通本地 smoke test,再把 `localhost` 换成工作流运行环境能够访问到的服务名或内网地址。
|
||||
当你希望让 Dify、n8n、HTTP 节点、webhook worker 或其他低代码工作流引擎调用私有 FunASR 语音 API 时,可以从这份配方开始。这些是 multipart HTTP 接入示例,不是对所有工作流产品或版本的兼容保证。
|
||||
|
||||
示例服务**没有内置鉴权或上传大小限制**。客户端 URL 中的 `localhost` 不会限制服务端监听地址。本地检查只绑定 loopback;共享前按[安全与网关指南](SECURITY_zh.md)配置 TLS、网关鉴权、上传/时限/速率限制、health/model/schema 私有访问和音频/转写保留策略。占位 API key 不会让服务具备鉴权。
|
||||
|
||||
## 服务预检
|
||||
|
||||
先按[示例 README](README_zh.md#快速开始)准备 checkout 和环境。其中固定的源码 revision 不是依赖/模型锁定,也不代表已验证全新安装。以下命令从该 checkout 根目录开始,假定 `.venv` 已准备好。如果服务已经运行,跳过启动直接检查,不要在同一端口启动第二个进程。
|
||||
|
||||
```bash
|
||||
cd examples/openai_api
|
||||
python server.py --model sensevoice --device cuda --port 8000
|
||||
source ../../.venv/bin/activate
|
||||
python server.py --host 127.0.0.1 --model sensevoice --device cpu --port 8000
|
||||
```
|
||||
|
||||
在工作流所在主机或容器中执行:
|
||||
准备好 CUDA 依赖后,可将 CPU 命令替换为 `python server.py --host 127.0.0.1 --model sensevoice --device cuda --port 8000`。另一个实现即打包的 `funasr-server` 路线见 [Agent 集成指南](../../docs/agent_integration_zh.md),其启动默认值、别名和响应字段与本示例不同。
|
||||
|
||||
在同一主机的第二个终端进入同一 checkout 的 `examples/openai_api` 目录,激活同一环境。等待模型加载后检查本地服务:
|
||||
|
||||
```bash
|
||||
export FUNASR_BASE_URL=http://<funasr-host>:8000
|
||||
source ../../.venv/bin/activate
|
||||
export FUNASR_BASE_URL="http://127.0.0.1:8000"
|
||||
curl -fsS "$FUNASR_BASE_URL/health"
|
||||
curl -fsS "$FUNASR_BASE_URL/v1/models"
|
||||
curl -fsS "$FUNASR_BASE_URL/openapi.json"
|
||||
```
|
||||
|
||||
如果工作流引擎运行在 Docker 中,`localhost` 通常指的是工作流容器自身。请改用 Docker Compose service name、Kubernetes service name 或内网主机名。
|
||||
健康与 schema 检查不验证声学正确性。下方转写命令的 `meeting.wav` 应替换为已有本地音频;README 的公开中文 smoke 样例不是多语言准确率基准。
|
||||
|
||||
如果工作流引擎运行在 Docker 中,`localhost` 通常指的是工作流容器自身;主机 loopback 服务不会自动对容器可达。请明确配置私有网关/容器网络,再将 `FUNASR_BASE_URL` 和 worker 的 `FUNASR_URL` 替换为工作流运行时能访问的地址。需要网关鉴权时使用真实凭据;下方本地 curl/Python 示例没有添加鉴权 header。不要为解决连通性而把未鉴权端口直接暴露到公网。
|
||||
|
||||
## Postman smoke test
|
||||
|
||||
在配置低代码工具前,可以先导入 [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)。
|
||||
处理离线多人会议时,先按 [MOSS 部署指南](../../docs/moss_transcribe_diarize_zh.md)准备独立的第三方 MOSS 服务,包括 GPU 和文件时长边界。再将 `MODEL_ALIAS` 改为 `moss-transcribe-diarize`,保留 `response_format=verbose_json` 以保留可用的原生匿名说话人 segments。不要添加外部 VAD 或 `spk=true`;录音内标签不是已验证身份,也不是跨录音稳定编号。只修改客户端别名不会完成服务环境准备。
|
||||
|
||||
## Multipart HTTP 请求
|
||||
|
||||
所有工作流引擎最终都需要发出下面这种请求:
|
||||
|
||||
| 字段 | 值 |
|
||||
|---|---|
|
||||
| Method | `POST` |
|
||||
| URL | `http://<funasr-host>:8000/v1/audio/transcriptions` |
|
||||
| Body type | `multipart/form-data` |
|
||||
| File field | `file` |
|
||||
| Text field | `model=sensevoice` |
|
||||
| Text field | `response_format=verbose_json` |
|
||||
| Timeout | 根据最长音频时长设置,例如长录音可先设为 300 秒。 |
|
||||
- **Method:** `POST`
|
||||
- **URL:** `http://<funasr-host>:8000/v1/audio/transcriptions`
|
||||
- **Body type:** `multipart/form-data`
|
||||
- **File field:** `file`
|
||||
- **Text field:** `model=sensevoice`
|
||||
- **Text field:** `response_format=verbose_json`
|
||||
- **Timeout:** 根据最长音频时长设置,例如长录音可先设为 300 秒。
|
||||
|
||||
等价 curl 命令:
|
||||
|
||||
```bash
|
||||
curl "$FUNASR_BASE_URL/v1/audio/transcriptions" \
|
||||
curl -fsS "$FUNASR_BASE_URL/v1/audio/transcriptions" \
|
||||
-F file=@meeting.wav \
|
||||
-F model=sensevoice \
|
||||
-F response_format=verbose_json
|
||||
```
|
||||
|
||||
常用响应字段映射:
|
||||
将 `text` 映射为转写文本。`response_format=verbose_json` 只选择格式,不开启说话人分离,也不强制生成时间戳。消费其他字段前,先看[客户端响应契约](CLIENTS.md#response-formats):
|
||||
|
||||
| 路径 | 用途 |
|
||||
|---|---|
|
||||
| `text` | 纯文本转写结果,可传给聊天机器人、工单、知识库或后续摘要节点。 |
|
||||
| `segments` | 请求 `verbose_json` 时返回的时间戳、说话人等分段信息。 |
|
||||
| `duration` | API 返回的音频处理时长,适合写入日志和监控。 |
|
||||
| `model` | 本次请求使用的模型别名。 |
|
||||
- **示例 `server.py`:** `segments` 只来自模型返回的 `sentence_info`,否则为 `segments=[]`。`duration` 是 `generate()` 调用周围的耗时秒数,不含初次模型加载,不是录音时长。`model` 是解析后的请求别名;`language` 回显提交的提示或 `auto`,不是检测出的语言。
|
||||
- **打包 `funasr-server`:** `duration` 是音频时长秒数,fallback 无法读取音频元数据时可能为 0。它可能返回按文本生成的粗粒度片段,不是强制对齐。verbose 响应有 `task` 和片段 `id`/`words`,但没有顶层 `model`;语言可使用后端检测结果。对非原生说话人模型,`spk=true` 请求独立说话人流程,仍依赖其模型和环境。示例服务没有 `spk` 表单字段。
|
||||
|
||||
片段 `start`/`end` 单位为秒,不是 SDK 的毫秒坐标。`speaker` 字段可能缺失、为 null、数字或字符串;标签不标识真实身份。非空片段或 `verbose_json` 都不保证准确字幕对齐。HTTP 展示文本会剥离 SenseVoice 富标签,不等于 SDK 原始标签结果,也不是独立的情绪/事件响应。SDK 字段/选项如 `timestamp`、`timestamps`、`ctc_timestamps`、`use_itn`、hotwords 和原始数组不是额外表单字段;原始 SDK 时间戳不会自动转成 HTTP segments。
|
||||
|
||||
以下是没有 `sentence_info` 的示例服务响应;`0.42` 是处理耗时,不是音频时长:
|
||||
|
||||
```json
|
||||
{"text": "recognized speech", "segments": [], "language": "auto", "duration": 0.42, "model": "sensevoice"}
|
||||
```
|
||||
|
||||
以下是 3.2 秒文件对应的打包服务响应示意,含粗粒度 fallback 片段,未开启说话人选项。这些示例说明 schema,不是新模型测量:
|
||||
|
||||
```json
|
||||
{
|
||||
"task": "transcribe",
|
||||
"language": "en",
|
||||
"duration": 3.2,
|
||||
"text": "recognized speech",
|
||||
"segments": [
|
||||
{"id": 0, "start": 0.0, "end": 3.2, "text": "recognized speech", "words": []}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Dify 自定义工具或 HTTP 节点
|
||||
|
||||
@@ -74,21 +99,33 @@ curl "$FUNASR_BASE_URL/v1/audio/transcriptions" \
|
||||
- Body: `multipart/form-data`
|
||||
- File part: `file`,绑定到上传音频变量
|
||||
- Text parts: `model=sensevoice`、`response_format=verbose_json`
|
||||
- Output variable: 把 `text` 映射为转写文本;需要时间戳或说话人信息时保留 `segments`
|
||||
- Output variable: 把 `text` 映射为转写文本;使用时间戳或说话人标签前检查 `segments` 是否可用及其来源
|
||||
|
||||
### 音频 URL 转写
|
||||
|
||||
有些工作流工具只能传文件 URL,而不能直接传 multipart 二进制。此时建议加一个内网 worker:
|
||||
有些工作流工具只能传文件 URL,而不能直接传 multipart 二进制。multipart `file` 字段中的 URL 字符串不是音频上传。优先直接上传二进制,或传受控存储对象 ID 并由经过审查的存储客户端解析。
|
||||
|
||||
1. Dify 将音频 URL 和元数据发送给 worker。
|
||||
下面的 sketch 仅用于运维人员批准的可信存储 URL。目标 allowlist、私网访问策略、重定向校验、下载字节上限与鉴权均**未实现**。`requests.get` 会跟随重定向并将整个响应缓存在内存中;其 timeout 不是字节限制,也不是完整的端到端截止时限。不要把用户提供的 URL 传给这个 helper。接受这些 URL 前,必须使用经过审查的下载边界执行全部控制,包括阻止非预期私网/元数据目标并检查每次重定向。位于可信内网并不能防止 SSRF。
|
||||
|
||||
对于这个可信输入示意:
|
||||
|
||||
1. 运维人员向 worker 提供已批准的音频 URL 和元数据。
|
||||
2. worker 从可信存储下载音频。
|
||||
3. worker 使用 multipart 请求调用 FunASR。
|
||||
4. worker 将 `text`、`segments` 和运行日志返回给 Dify。
|
||||
4. worker 返回服务 JSON,由下游检查可选字段。日志不得暴露签名 URL、凭据或私有转写。
|
||||
|
||||
在同一已激活的客户端环境安装独立 HTTP 依赖:
|
||||
|
||||
```bash
|
||||
python -m pip install requests
|
||||
```
|
||||
|
||||
按已准备的服务设置 `FUNASR_URL`;默认值用于同主机 worker。工作流可导入以下函数定义,但它们不创建 HTTP 监听器,也不实现入站鉴权/上传限制。
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
FUNASR_URL = "http://funasr-api:8000/v1/audio/transcriptions"
|
||||
FUNASR_URL = "http://127.0.0.1:8000/v1/audio/transcriptions"
|
||||
|
||||
def transcribe_from_url(audio_url: str) -> dict:
|
||||
audio_response = requests.get(audio_url, timeout=120)
|
||||
@@ -100,7 +137,7 @@ def transcribe_from_url(audio_url: str) -> dict:
|
||||
return response.json()
|
||||
```
|
||||
|
||||
请把这个 worker 放在可信内网中,并在下载用户提供的链接前校验允许访问的域名或存储桶。
|
||||
请把此示意限制在已批准输入内;仅校验主机名不是完整的安全下载策略。
|
||||
|
||||
## n8n HTTP Request 节点
|
||||
|
||||
@@ -108,37 +145,31 @@ def transcribe_from_url(audio_url: str) -> dict:
|
||||
|
||||
推荐 HTTP Request 配置:
|
||||
|
||||
| n8n 设置 | 值 |
|
||||
|---|---|
|
||||
| Method | `POST` |
|
||||
| URL | `http://<funasr-host>:8000/v1/audio/transcriptions` |
|
||||
| Send Body | enabled |
|
||||
| Body Content Type | `Form-Data` / multipart |
|
||||
| Binary file field | `file` |
|
||||
| Additional form fields | `model=sensevoice`、`response_format=verbose_json` |
|
||||
| Response Format | JSON |
|
||||
| Timeout | 长录音场景需要调大。 |
|
||||
- **Method:** `POST`
|
||||
- **URL:** `http://<funasr-host>:8000/v1/audio/transcriptions`
|
||||
- **Send Body:** enabled
|
||||
- **Body Content Type:** `Form-Data` / multipart
|
||||
- **Binary file field:** `file`
|
||||
- **Additional form fields:** `model=sensevoice`、`response_format=verbose_json`
|
||||
- **Response Format:** JSON
|
||||
- **Timeout:** 长录音场景需要调大。
|
||||
|
||||
请求之后,使用 `{{$json.text}}` 作为转写文本。如果启用了 `verbose_json`,可以把 `{{$json.segments}}` 传给字幕、说话人分析或质检节点。
|
||||
请求之后,使用 `{{$json.text}}` 作为转写文本。先检查 `{{$json.segments}}` 存在且适合任务,再传给后续节点;空片段或粗粒度片段不能作为已验证字幕时刻或说话人分离结果。节点标签和二进制属性配置随所安装的 n8n 版本变化;`file` 是发出的 multipart 字段名,不一定是传入二进制属性的名字。
|
||||
|
||||
### n8n OpenAI Audio 节点
|
||||
|
||||
也可以让内置 OpenAI 节点的 Audio > Transcribe 操作调用 FunASR。在 OpenAI
|
||||
凭据中将 Base URL 设为 `http://<funasr-host>:8000/v1`,并填写任意非空 API
|
||||
key。该节点固定发送 `model=whisper-1`,FunASR 会将这个兼容别名映射到服务
|
||||
启动时选择的模型。此路径只返回转写文本;需要 `verbose_json`、分段或说话人
|
||||
标签时,仍使用上面的 HTTP Request 节点。
|
||||
对于发送 `model=whisper-1` 的 OpenAI Audio > Transcribe 节点版本,FunASR 会把该兼容别名映射到服务启动时选择的模型,而不是选择 Whisper checkpoint。Base URL 使用可达服务地址并带 `/v1`。非空占位 key 仅适用于未保护的本地端点,访问鉴权网关时应提供真实凭据。请核验所安装节点版本的请求行为,不要假定所有版本相同。此配方用于纯文本转写;需要显式 `response_format` 或服务支持的说话人选项时使用 HTTP Request 节点,仍受上述响应边界限制。
|
||||
|
||||
## Webhook worker 模式
|
||||
|
||||
当工作流引擎不能稳定发送 multipart 文件,或音频需要预处理时,可以把转写封装成一个内部 webhook worker。
|
||||
当工作流引擎不能稳定发送 multipart 文件,或音频需要预处理时,可以使用这个函数。此 POSIX 临时文件示例使用同一个 `requests` 依赖,并关闭上传句柄。它接受已在内存中的 bytes,应在缓冲之前限制上传大小;它是函数,不是受保护的 webhook 服务。
|
||||
|
||||
```python
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
import requests
|
||||
|
||||
FUNASR_URL = "http://localhost:8000/v1/audio/transcriptions"
|
||||
FUNASR_URL = "http://127.0.0.1:8000/v1/audio/transcriptions"
|
||||
|
||||
def transcribe_bytes(filename: str, payload: bytes, content_type: str = "audio/wav") -> dict:
|
||||
with tempfile.NamedTemporaryFile(suffix=Path(filename).suffix or ".wav") as tmp:
|
||||
@@ -155,7 +186,7 @@ def transcribe_bytes(filename: str, payload: bytes, content_type: str = "audio/w
|
||||
return response.json()
|
||||
```
|
||||
|
||||
这个 worker 也适合集中做音频转码、文件大小限制、请求 ID、鉴权、重试和审计日志。
|
||||
这里未实现音频转码、文件大小检查、请求 ID、入站/上游鉴权和重试策略。共享前在相应边界执行这些控制;重试可能重复执行昂贵的转写工作。
|
||||
|
||||
## 生产环境护栏
|
||||
|
||||
@@ -168,11 +199,9 @@ def transcribe_bytes(filename: str, payload: bytes, content_type: str = "audio/w
|
||||
|
||||
## 故障排查
|
||||
|
||||
| 现象 | 处理方式 |
|
||||
|---|---|
|
||||
| 工作流能访问 `/health`,但转写失败 | 确认请求是 `multipart/form-data`,且二进制字段名是 `file`。 |
|
||||
| Dify 或 n8n 访问 `localhost` 失败 | 换成工作流运行时可访问的主机名、Compose service name 或 Kubernetes service name。 |
|
||||
| 响应中没有 `segments` | 设置 `response_format=verbose_json`。 |
|
||||
| 请求超时 | 调大 HTTP timeout,或先切分长录音。 |
|
||||
| 第一次请求很慢 | 使用 `--model sensevoice` 预加载模型,并用 `/health` 做 readiness check。 |
|
||||
| 模型别名未知 | 调用 `/v1/models`,使用返回列表中的别名。 |
|
||||
- **工作流能访问 `/health`,但转写失败:** 确认请求是 `multipart/form-data`,且二进制字段名是 `file`。
|
||||
- **Dify 或 n8n 访问 `localhost` 失败:** 换成工作流运行时可访问的主机名、Compose service name 或 Kubernetes service name。
|
||||
- **响应中没有可用 `segments`:** 检查格式和已部署 schema,再检查模型的 `sentence_info` 与说话人配置;仅设置 `verbose_json` 不能创建时间戳或标签。
|
||||
- **请求超时:** 调大 HTTP timeout,或先切分长录音。
|
||||
- **第一次请求很慢:** 使用 `--model sensevoice` 预加载模型,并用 `/health` 做 readiness check。
|
||||
- **模型别名未知:** 调用 `/v1/models`,使用返回列表中的别名。
|
||||
|
||||
Reference in New Issue
Block a user