docs: clarify low-code workflow deployment and response contracts

Signed-off-by: zhifu gao <[email protected]>
This commit is contained in:
zhifu gao
2026-09-07 16:08:47 +00:00
parent 42ee5421fc
commit 160494c4c4
3 changed files with 375 additions and 119 deletions
+87 -61
View File
@@ -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.
+87 -58
View File
@@ -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`,使用返回列表中的别名。