mirror of
https://github.com/modelscope/FunASR.git
synced 2026-10-12 02:19:18 +00:00
docs: restore maintained bilingual Agent integration routes
Signed-off-by: zhifu gao <[email protected]>
This commit is contained in:
@@ -44,6 +44,7 @@
|
||||
Python SDK, Python HTTP, native vLLM HTTP and C++ WebSocket are different
|
||||
interfaces. Choose the matching protocol before selecting a client.
|
||||
|
||||
- [Agent integration](agent_integration.md) / [中文](agent_integration_zh.md): HTTP, MCP, desktop recording and local subtitles
|
||||
- [HTTP schema](../examples/openai_api/OPENAPI.md), [JavaScript](../examples/openai_api/JAVASCRIPT.md), [workflow integration](../examples/openai_api/WORKFLOWS.md)
|
||||
- [C++ WebSocket protocol](../runtime/docs/websocket_protocol.md) and [ONNX binary output](../runtime/docs/onnxruntime_binary_output.md)
|
||||
- [HTTP security](../examples/openai_api/SECURITY.md) and [Kubernetes manifests](../examples/openai_api/kubernetes/README.md)
|
||||
|
||||
@@ -0,0 +1,224 @@
|
||||
# FunASR Agent Integration
|
||||
|
||||
[中文](agent_integration_zh.md)
|
||||
|
||||
Connect a speech application through the interface it actually needs: an HTTP
|
||||
file-transcription request, a local MCP tool, desktop recording, or a local
|
||||
subtitle pipeline. These paths do not share every model, option, or result field.
|
||||
For direct in-process inference, use the [Python SDK](python_api.md).
|
||||
|
||||
## HTTP server
|
||||
|
||||
The following source-based starting point includes the example scripts used
|
||||
later on this page. The commands use a POSIX shell. Use a new checkout and virtual environment; installing the
|
||||
PyPI package alone does not install these repository examples.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/modelscope/FunASR.git FunASR-agent
|
||||
cd FunASR-agent
|
||||
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
|
||||
```
|
||||
|
||||
This pins the source, not all dependencies or model weights. Use the
|
||||
[installation guide](installation/installation.md) to prepare the selected
|
||||
CPU/GPU environment, record resolved package/model versions, and validate a
|
||||
real request. `pip check` alone is not a CUDA, audio-decoder, or clean-install test.
|
||||
|
||||
From that environment, run **one** of these commands. The CPU recipe selects
|
||||
SenseVoice explicitly; the CUDA alternative needs a working GPU environment.
|
||||
Keep this terminal running and use another prepared terminal for clients.
|
||||
|
||||
```bash
|
||||
funasr-server --host 127.0.0.1 --device cpu --model sensevoice --port 8000
|
||||
# Alternative: stop the CPU server before using the same port.
|
||||
funasr-server --host 127.0.0.1 --device cuda --model sensevoice --port 8000
|
||||
```
|
||||
|
||||
```bash
|
||||
curl -fsS http://localhost:8000/health
|
||||
curl -fsS http://localhost:8000/v1/models
|
||||
```
|
||||
|
||||
Use `/v1/audio/transcriptions` for uploaded files, `/openapi.json` for the running
|
||||
service schema, and `/docs` for its Swagger UI. This service `/docs` is not the
|
||||
FunASR website's documentation directory. Health or model-list responses do not
|
||||
replace a successful transcription with the intended model.
|
||||
|
||||
The packaged `funasr-server` and [example HTTP server](../examples/openai_api/README.md#api-contract)
|
||||
have different defaults, aliases, and response schemas. Specify `model` in the
|
||||
request as well as at startup. `paraformer-en` is an example-server alias, not a
|
||||
built-in packaged-server alias. SenseVoice HTTP display text has rich tags removed;
|
||||
it is not a dedicated emotion/event output API. Nano and MLT-Nano language coverage
|
||||
and timestamp paths must be selected through the [model guide](model_selection.md),
|
||||
not inferred from the HTTP interface. For a custom packaged-server model, use
|
||||
`--model-path` with the appropriate `--hub` and request `model=custom`; an
|
||||
arbitrary model ID is not automatically a built-in `--model` alias.
|
||||
|
||||
[MOSS-Transcribe-Diarize](moss_transcribe_diarize.md) is a third-party OpenMOSS
|
||||
model with its own deployment requirements. Its native anonymous speaker labels
|
||||
do not require external VAD or an external speaker model; do not add those stages
|
||||
or assume every client exposes all of its output.
|
||||
|
||||
The local server does not authenticate the placeholder SDK key below. Before
|
||||
network access, add TLS, authentication, upload limits and rate limits according
|
||||
to the [security guide](../examples/openai_api/SECURITY.md). CORS is not authentication.
|
||||
|
||||
## SDK and curl
|
||||
|
||||
Install the separate OpenAI HTTP client in the client environment:
|
||||
|
||||
```bash
|
||||
python -m pip install openai
|
||||
```
|
||||
|
||||
```python
|
||||
from openai import OpenAI
|
||||
|
||||
client = OpenAI(base_url="http://localhost:8000/v1", api_key="local-development")
|
||||
with open("meeting.wav", "rb") as audio:
|
||||
result = client.audio.transcriptions.create(
|
||||
model="sensevoice",
|
||||
file=audio,
|
||||
response_format="verbose_json",
|
||||
)
|
||||
print(result.text)
|
||||
for segment in getattr(result, "segments", []):
|
||||
print(segment)
|
||||
```
|
||||
|
||||
```bash
|
||||
curl -fsS http://localhost:8000/v1/audio/transcriptions \
|
||||
-F file=@audio.wav \
|
||||
-F model=sensevoice \
|
||||
-F response_format=verbose_json
|
||||
```
|
||||
|
||||
`verbose_json` chooses a format; it does not enable diarization, recover rich
|
||||
tags, or guarantee word alignment. The example server copies available
|
||||
`sentence_info` into segments, otherwise returning an empty list. The packaged
|
||||
server can supply coarse fallback segments. Both expose segment times in seconds,
|
||||
but their `duration` fields differ. Use the [client result contract](../examples/openai_api/CLIENTS.md#response-formats)
|
||||
before consuming timestamps. `json` and `text` are simpler response choices.
|
||||
Speaker processing via `spk=true` belongs to the packaged API, not the example's
|
||||
request schema; see [speaker labels and identity limits](speaker_emotion.md).
|
||||
|
||||
## Workflow integrations
|
||||
|
||||
Use a multipart HTTP node with method `POST`, the transcription endpoint, a
|
||||
binary file field named `file`, and text fields `model` and `response_format`.
|
||||
A file URL in the `file` field is not equivalent to uploading audio bytes.
|
||||
For Dify/n8n running in a container, `localhost` means that container, not the
|
||||
FunASR host; configure a reachable service address behind the intended gateway.
|
||||
|
||||
- [Dify, n8n and webhook workers](../examples/openai_api/WORKFLOWS.md) provide request wiring examples.
|
||||
- [JavaScript and TypeScript](../examples/openai_api/JAVASCRIPT.md) cover SDK and multipart clients.
|
||||
- [Postman](../examples/openai_api/POSTMAN.md) and the [smoke test](../examples/openai_api/smoke_test.py) check a deployed endpoint.
|
||||
- [Gradio](../examples/openai_api/GRADIO.md) provides a browser upload/microphone example.
|
||||
- [OpenAPI](../examples/openai_api/OPENAPI.md) distinguishes the checked-in example schema from the deployed packaged schema.
|
||||
|
||||
Register transcription as a tool in the host framework using these request and
|
||||
result boundaries. The URL worker example does not implement a complete secure
|
||||
downloader: add destination allowlists, private-network blocking, redirect
|
||||
validation, size limits and timeouts before accepting untrusted URLs. Use the
|
||||
client result contract above instead of assuming the workflow field table fits
|
||||
both server implementations. These recipes are not proof that every framework version has
|
||||
been integrated or that untrusted download URLs are safe.
|
||||
|
||||
## MCP server
|
||||
|
||||
From the prepared repository root and environment:
|
||||
|
||||
```bash
|
||||
python examples/mcp_server/funasr_mcp.py
|
||||
```
|
||||
|
||||
Prepare PyTorch and a compatible audio-feature backend using the installation
|
||||
guide before attempting transcription. Installing the toolkit or completing an
|
||||
MCP handshake alone does not verify model execution; this script does not need
|
||||
a separate MCP SDK package. An MCP client launches it over stdio, not HTTP.
|
||||
Configure absolute paths to the prepared Python environment and checkout:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"funasr": {
|
||||
"command": "/path/to/FunASR-agent/.venv/bin/python",
|
||||
"args": ["/path/to/FunASR-agent/examples/mcp_server/funasr_mcp.py"],
|
||||
"env": {
|
||||
"FUNASR_DEVICE": "cpu",
|
||||
"FUNASR_MODEL": "iic/SenseVoiceSmall"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`transcribe_audio` accepts an existing local `audio_path` visible to the server,
|
||||
including a read-only mounted path in a container. It does not accept URLs or
|
||||
live streams. The first call may download and load weights. The language hints
|
||||
are `auto`, `zh`, `yue`, `en`, `ja`, and `ko`; setting `FUNASR_MODEL` does not
|
||||
change that tool schema or guarantee another model is compatible with its VAD path.
|
||||
|
||||
The result is formatted MCP `content` with `type=text`, optionally including
|
||||
segments, not the HTTP response object. Top-level transcript rich tags are
|
||||
removed; optional segment text is copied from model output. `FUNASR_DEVICE`
|
||||
defaults to `cpu`, and `FUNASR_MODEL` defaults to `iic/SenseVoiceSmall`.
|
||||
See the [MCP source and container setup](../examples/mcp_server/README.md) for
|
||||
client configuration and filesystem mounts. Control which files the assistant
|
||||
and server can access; a local tool is not a filesystem authorization boundary.
|
||||
|
||||
## Desktop voice input
|
||||
|
||||
With the HTTP server already running, use a second terminal in the prepared checkout:
|
||||
|
||||
```bash
|
||||
python -m pip install sounddevice numpy pyperclip openai pynput
|
||||
python examples/voice_input/funasr_input.py --server http://localhost:8000/v1 --model sensevoice
|
||||
```
|
||||
|
||||
The script toggles recording, uploads WAV audio to the HTTP service, and copies
|
||||
the transcript for pasting. Microphone permissions and audio-device support are
|
||||
required; macOS may also require accessibility permissions, and Linux automatic
|
||||
paste uses `xdotool`. Clipboard/paste behavior varies by desktop session.
|
||||
The current `--lang` option is parsed but not forwarded to the transcription
|
||||
request, so it is not an effective language control in this path.
|
||||
|
||||
A remote `--server` sends the recorded audio to that endpoint. Do not treat this
|
||||
as an unconditional offline/privacy guarantee or a measured latency promise.
|
||||
See [configuration](../examples/voice_input/README.md#配置选项) and the
|
||||
[implementation](../examples/voice_input/funasr_input.py) before deployment.
|
||||
|
||||
## Subtitle generation
|
||||
|
||||
This is a local `AutoModel` pipeline, not an HTTP or MCP client. From the prepared
|
||||
checkout, with local input files and a suitable inference environment:
|
||||
|
||||
```bash
|
||||
python examples/subtitle/generate_subtitle.py video.mp4
|
||||
python examples/subtitle/generate_subtitle.py meeting.wav --spk
|
||||
python examples/subtitle/generate_subtitle.py podcast.mp3 --format vtt
|
||||
python examples/subtitle/generate_subtitle.py audio.wav --device cpu
|
||||
```
|
||||
|
||||
The default device is CUDA; the last command explicitly selects CPU. The default
|
||||
model is SenseVoiceSmall with VAD and punctuation. This fixed pipeline is not a
|
||||
generic recipe for arbitrary models. `--spk` adds
|
||||
CAM++ anonymous labels, not verified identities. `--format` selects SRT or VTT;
|
||||
`--output` chooses a path, and existing output files are overwritten. Choose a
|
||||
new output path when preserving prior subtitles. `--lang` passes a non-auto language hint to generation.
|
||||
`--max-single-segment-time` is in milliseconds, with current default `60000`.
|
||||
|
||||
`--segment-mode readable` groups display cues without rewriting recognized
|
||||
text or punctuation; `sentence` retains raw model sentence grouping. Neither
|
||||
mode fixes punctuation errors or guarantees phonetic boundaries. Check actual
|
||||
timestamp availability and playback against the original audio: a missing timing
|
||||
result can fall back to a zero-duration `(0, 0)` interval, not a validated subtitle.
|
||||
Input decoding,
|
||||
model/dependency loading and GPU capacity still require environment-specific
|
||||
validation. See [subtitle options](../examples/subtitle/README.md#options) and
|
||||
the [speaker guide](speaker_emotion.md) for output interpretation.
|
||||
@@ -0,0 +1,201 @@
|
||||
# FunASR Agent 集成
|
||||
|
||||
[English](agent_integration.md)
|
||||
|
||||
按应用需要选择接口:HTTP 文件转写、本地 MCP 工具、桌面录音或本地字幕流水线。
|
||||
这些路径的模型、选项和输出字段并不完全相同。直接在进程内推理请使用
|
||||
[Python SDK](python_api_zh.md)。
|
||||
|
||||
## HTTP 服务
|
||||
|
||||
以下源码安装起点包含本页后续用到的示例脚本,命令使用 POSIX shell。
|
||||
使用新目录和虚拟环境;仅安装
|
||||
PyPI 包不会安装这些仓库示例。
|
||||
|
||||
```bash
|
||||
git clone https://github.com/modelscope/FunASR.git FunASR-agent
|
||||
cd FunASR-agent
|
||||
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
|
||||
```
|
||||
|
||||
这只固定源码,不会锁定所有依赖和模型权重。按照[安装指南](installation/installation_zh.md)
|
||||
准备 CPU/GPU 环境,记录实际依赖版本和模型版本,再验证真实请求。
|
||||
`pip check` 不能代替 CUDA、音频解码或全新环境安装测试。
|
||||
|
||||
在该环境中选择以下**一条**命令运行。CPU 示例显式选择 SenseVoice;CUDA 方案需要
|
||||
可用的 GPU 环境。保持服务终端运行,在另一个已准备好的终端执行客户端命令。
|
||||
|
||||
```bash
|
||||
funasr-server --host 127.0.0.1 --device cpu --model sensevoice --port 8000
|
||||
# 备选:使用相同端口前先停止 CPU 服务。
|
||||
funasr-server --host 127.0.0.1 --device cuda --model sensevoice --port 8000
|
||||
```
|
||||
|
||||
```bash
|
||||
curl -fsS http://localhost:8000/health
|
||||
curl -fsS http://localhost:8000/v1/models
|
||||
```
|
||||
|
||||
上传文件使用 `/v1/audio/transcriptions`,运行中服务的 schema 位于 `/openapi.json`,
|
||||
Swagger UI 位于 `/docs`。这个服务的 `/docs` 不是 FunASR 官网文档目录。
|
||||
健康检查或模型列表返回成功,不能代替目标模型的真实转写验收。
|
||||
|
||||
打包的 `funasr-server` 与[示例 HTTP 服务](../examples/openai_api/README_zh.md#api-contract)
|
||||
具有不同的默认值、别名和响应结构。启动和请求时都应显式指定 `model`。
|
||||
`paraformer-en` 是示例服务的注册别名,不是打包服务内置别名。
|
||||
SenseVoice 的 HTTP 展示文本已清除富标签,不是专用的情感或事件输出 API。
|
||||
Nano 与 MLT-Nano 的语种和时间戳能力应按[模型选择指南](model_selection_zh.md)
|
||||
及实际运行路径区分,不能从 HTTP 接口名称推断。打包服务的自定义模型应通过
|
||||
`--model-path` 配合正确的 `--hub` 加载,并在请求中指定 `model=custom`;
|
||||
任意模型 ID 不会自动成为内置 `--model` 别名。
|
||||
|
||||
[MOSS-Transcribe-Diarize](moss_transcribe_diarize_zh.md) 是 OpenMOSS 的第三方模型,
|
||||
有独立部署要求。其原生匿名说话人标签不需要外接 VAD 或说话人模型,不要叠加这些阶段,
|
||||
也不要假设所有客户端都能呈现其全部输出。
|
||||
|
||||
本地服务不会认证下方 SDK 的占位 key。对外提供网络访问前,按
|
||||
[安全指南](../examples/openai_api/SECURITY_zh.md) 配置 TLS、鉴权、上传大小及速率限制。
|
||||
CORS 不是身份认证。
|
||||
|
||||
## SDK 与 curl
|
||||
|
||||
在客户端环境单独安装 OpenAI HTTP 客户端:
|
||||
|
||||
```bash
|
||||
python -m pip install openai
|
||||
```
|
||||
|
||||
```python
|
||||
from openai import OpenAI
|
||||
|
||||
client = OpenAI(base_url="http://localhost:8000/v1", api_key="local-development")
|
||||
with open("meeting.wav", "rb") as audio:
|
||||
result = client.audio.transcriptions.create(
|
||||
model="sensevoice",
|
||||
file=audio,
|
||||
response_format="verbose_json",
|
||||
)
|
||||
print(result.text)
|
||||
for segment in getattr(result, "segments", []):
|
||||
print(segment)
|
||||
```
|
||||
|
||||
```bash
|
||||
curl -fsS http://localhost:8000/v1/audio/transcriptions \
|
||||
-F file=@audio.wav \
|
||||
-F model=sensevoice \
|
||||
-F response_format=verbose_json
|
||||
```
|
||||
|
||||
`verbose_json` 只选择格式,不会开启说话人分离、恢复富标签或保证词级对齐。
|
||||
示例服务把已有 `sentence_info` 转为 segments,没有时返回空列表;打包服务可能生成
|
||||
粗粒度回退分段。二者的分段时间均为秒,但 `duration` 字段语义不同。
|
||||
使用时间戳前请阅读[客户端输出契约](../examples/openai_api/CLIENTS.md#response-formats)。
|
||||
`json` 和 `text` 是更简单的响应格式。`spk=true` 的说话人处理属于打包 API,
|
||||
不属于示例服务的请求 schema;另见[说话人标签与身份边界](speaker_emotion_zh.md)。
|
||||
|
||||
## 工作流集成
|
||||
|
||||
HTTP 节点使用 `POST` 转写端点和 multipart 请求体:二进制文件字段为 `file`,
|
||||
文本字段为 `model` 与 `response_format`。把音频 URL 写入 `file` 字段不等于上传音频字节。
|
||||
当 Dify/n8n 运行在容器中时,`localhost` 指该容器而非 FunASR 主机,
|
||||
应配置经过预期网关、工作流实际可达的服务地址。
|
||||
|
||||
- [Dify、n8n 与 webhook worker](../examples/openai_api/WORKFLOWS_zh.md) 提供请求连接示例。
|
||||
- [JavaScript 和 TypeScript](../examples/openai_api/JAVASCRIPT_zh.md) 提供 SDK 和 multipart 客户端。
|
||||
- [Postman](../examples/openai_api/POSTMAN_zh.md) 与[冒烟测试](../examples/openai_api/smoke_test.py) 检查已部署端点。
|
||||
- [Gradio](../examples/openai_api/GRADIO_zh.md) 提供浏览器上传及麦克风示例。
|
||||
- [OpenAPI](../examples/openai_api/OPENAPI_zh.md) 区分仓库内示例 schema 与已部署打包服务的 schema。
|
||||
|
||||
在宿主框架中按这些请求和输出边界注册转写工具。URL worker 示例不是完整的安全下载器;
|
||||
接受不可信 URL 前,应补充目标白名单、私网阻断、重定向校验、大小限制和超时。
|
||||
响应字段以上方客户端契约为准,不要假设工作流字段表适用于两种服务实现。
|
||||
这些示例不代表所有框架版本均已完成
|
||||
集成验证,也不代表任意不可信下载 URL 都是安全的。
|
||||
|
||||
## MCP 服务
|
||||
|
||||
在已准备好的仓库根目录和环境中运行:
|
||||
|
||||
```bash
|
||||
python examples/mcp_server/funasr_mcp.py
|
||||
```
|
||||
|
||||
执行转写前,应按安装指南准备 PyTorch 和兼容的音频特征后端。
|
||||
安装工具包或完成 MCP 握手,都不等于模型执行已通过验证;此脚本不需要额外安装 MCP SDK。
|
||||
MCP 客户端通过 stdio 启动此脚本,它不是 HTTP 监听服务。
|
||||
配置时使用已准备好的 Python 环境与仓库绝对路径:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"funasr": {
|
||||
"command": "/path/to/FunASR-agent/.venv/bin/python",
|
||||
"args": ["/path/to/FunASR-agent/examples/mcp_server/funasr_mcp.py"],
|
||||
"env": {
|
||||
"FUNASR_DEVICE": "cpu",
|
||||
"FUNASR_MODEL": "iic/SenseVoiceSmall"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`transcribe_audio` 接受服务端可见、已存在的本地 `audio_path`,也可以是容器中
|
||||
只读挂载的路径,不接受 URL 或实时流。首次调用可能下载并加载权重。
|
||||
语种提示为 `auto`、`zh`、`yue`、`en`、`ja`、`ko`;设置 `FUNASR_MODEL` 不会改变
|
||||
工具 schema,也不能保证另一模型兼容该工具的 VAD 路径。
|
||||
|
||||
结果被格式化为 MCP `content` 中的 `type=text`,可包含分段,而不是 HTTP 响应对象。
|
||||
顶层转写文本会清除富标签;可选的分段文本来自模型输出。
|
||||
`FUNASR_DEVICE` 默认为 `cpu`,`FUNASR_MODEL` 默认为 `iic/SenseVoiceSmall`。
|
||||
[MCP 源码与容器说明](../examples/mcp_server/README.md) 提供客户端配置和文件挂载方法。
|
||||
控制助手及服务端可访问的文件;本地工具本身不是文件系统权限隔离机制。
|
||||
|
||||
## 桌面语音输入
|
||||
|
||||
保持 HTTP 服务运行,在已准备好的仓库内打开另一个终端:
|
||||
|
||||
```bash
|
||||
python -m pip install sounddevice numpy pyperclip openai pynput
|
||||
python examples/voice_input/funasr_input.py --server http://localhost:8000/v1 --model sensevoice
|
||||
```
|
||||
|
||||
脚本切换录音状态,将 WAV 上传至 HTTP 服务,再复制转写结果供粘贴。
|
||||
需要麦克风权限和音频设备支持;macOS 还可能需要辅助功能权限,Linux 自动粘贴使用
|
||||
`xdotool`,剪贴板及粘贴行为随桌面会话而异。当前 `--lang` 虽然被解析,却没有传入
|
||||
转写请求,因此在此路径中不是有效的语种控制项。
|
||||
|
||||
远程 `--server` 会把录音发送给该端点,不能无条件宣称完全离线、音频不离开本机或
|
||||
达到固定延迟。部署前请阅读[配置选项](../examples/voice_input/README.md#配置选项)
|
||||
及[实现](../examples/voice_input/funasr_input.py)。
|
||||
|
||||
## 字幕生成
|
||||
|
||||
这是本地 `AutoModel` 流水线,不是 HTTP 或 MCP 客户端。
|
||||
在已准备好的仓库中,使用本地输入文件及适合的推理环境:
|
||||
|
||||
```bash
|
||||
python examples/subtitle/generate_subtitle.py video.mp4
|
||||
python examples/subtitle/generate_subtitle.py meeting.wav --spk
|
||||
python examples/subtitle/generate_subtitle.py podcast.mp3 --format vtt
|
||||
python examples/subtitle/generate_subtitle.py audio.wav --device cpu
|
||||
```
|
||||
|
||||
默认设备为 CUDA,最后一条命令显式选择 CPU。默认模型为 SenseVoiceSmall,并使用
|
||||
VAD 和标点模型;这套固定流水线不是任意模型的通用配方。`--spk` 添加 CAM++ 匿名标签,
|
||||
不验证真实身份。`--format` 选择 SRT/VTT,`--output` 指定输出路径,已有输出文件会被覆盖;
|
||||
需要保留旧字幕时应指定新路径。`--lang` 将非 auto
|
||||
语种提示传给推理。`--max-single-segment-time` 的单位是毫秒,当前默认 `60000`。
|
||||
|
||||
`--segment-mode readable` 对展示字幕分组,不改写识别文本或标点;`sentence` 保留
|
||||
原始模型句段分组。两种模式都不修复标点错误,也不保证音素级边界。
|
||||
应检查实际时间戳是否可用,并对照原音频回放;缺少时间信息时可能回退到零时长 `(0, 0)`
|
||||
区间,这不是经过验证的字幕。输入解码、模型与依赖加载、GPU 容量
|
||||
仍需针对环境验证。输出语义见[字幕选项](../examples/subtitle/README.md#options)
|
||||
及[说话人指南](speaker_emotion_zh.md)。
|
||||
@@ -69,6 +69,8 @@ entry to these guides. Source Markdown remains in this repository.
|
||||
:maxdepth: 1
|
||||
:caption: Applications and Ecosystem
|
||||
|
||||
agent_integration
|
||||
agent_integration_zh
|
||||
use_case_showcase
|
||||
use_case_showcase_zh
|
||||
community_projects
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
{"slug": "security", "group": "operate", "zh": "服务安全边界", "en": "Service security boundaries", "source_zh": "examples/openai_api/SECURITY_zh.md", "source_en": "examples/openai_api/SECURITY.md"},
|
||||
{"slug": "benchmark-method", "group": "operate", "zh": "可复现性能评测", "en": "Reproducible performance tests", "source_zh": "docs/benchmark/rtf_reproducibility.md", "source_en": "docs/benchmark/rtf_reproducibility.md"},
|
||||
{"slug": "realtime-benchmark", "group": "operate", "zh": "实时 WebSocket 压测", "en": "Realtime WebSocket benchmarking", "source_zh": "docs/benchmark/realtime_ws_benchmark.md", "source_en": "docs/benchmark/realtime_ws_benchmark.md"},
|
||||
{"slug": "agent-integration", "group": "ecosystem", "zh": "Agent 集成", "en": "Agent integration", "source_zh": "docs/agent_integration_zh.md", "source_en": "docs/agent_integration.md"},
|
||||
{"slug": "use-cases", "group": "ecosystem", "zh": "应用场景与示例", "en": "Use cases & examples", "source_zh": "docs/use_case_showcase_zh.md", "source_en": "docs/use_case_showcase.md"},
|
||||
{"slug": "community", "group": "ecosystem", "zh": "社区集成", "en": "Community integrations", "source_zh": "docs/community_projects_zh.md", "source_en": "docs/community_projects.md"},
|
||||
{"slug": "repositories", "group": "ecosystem", "zh": "四仓职责与贡献入口", "en": "Repositories & contribution paths", "source_zh": "docs/repository_roles_zh.md", "source_en": "docs/repository_roles.md"},
|
||||
|
||||
@@ -140,6 +140,22 @@
|
||||
"checklist": "readiness-checklist",
|
||||
"help": "when-to-open-an-issue"
|
||||
},
|
||||
"agent.html": {
|
||||
"server": "http-server",
|
||||
"sdk": "sdk-and-curl",
|
||||
"workflows": "workflow-integrations",
|
||||
"mcp": "mcp-server",
|
||||
"voice": "desktop-voice-input",
|
||||
"subtitle": "subtitle-generation"
|
||||
},
|
||||
"zh/agent.html": {
|
||||
"server": "http-服务",
|
||||
"sdk": "sdk-与-curl",
|
||||
"workflows": "工作流集成",
|
||||
"mcp": "mcp-服务",
|
||||
"voice": "桌面语音输入",
|
||||
"subtitle": "字幕生成"
|
||||
},
|
||||
"zh/deployment-matrix.html": {
|
||||
"matrix": "快速决策表",
|
||||
"choices": "常见选择",
|
||||
|
||||
@@ -17,6 +17,7 @@ ALIASES = {
|
||||
'moss-transcribe-diarize': 'moss-transcribe-diarize.html',
|
||||
'vllm': 'vllm.html',
|
||||
'deployment-matrix': 'deployment-matrix.html',
|
||||
'agent-integration': 'agent.html',
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -92,3 +92,37 @@ test('public speech sample is playable and waveform has actual pixels', async ({
|
||||
await page.locator('audio').evaluate((audio: HTMLAudioElement) => audio.pause());
|
||||
expect(await page.locator('.hero-image').evaluate((image: HTMLImageElement) => image.naturalWidth)).toBeGreaterThan(1000);
|
||||
});
|
||||
|
||||
for (const prefix of ['', '/en']) {
|
||||
for (const width of [390, 1440]) {
|
||||
test(`Agent integration is discoverable and readable ${prefix || '/zh'} ${width}`, async ({ page, context }) => {
|
||||
await context.grantPermissions(['clipboard-read', 'clipboard-write']);
|
||||
await page.setViewportSize({ width, height: 900 });
|
||||
const errors: string[] = [];
|
||||
page.on('pageerror', error => errors.push(error.message));
|
||||
await page.goto(`${prefix}/docs/`);
|
||||
await page.locator('[data-doc-search] input').fill('Agent');
|
||||
const route = `${prefix}/docs/agent-integration.html`;
|
||||
await page.locator(`.search-results a[href="${route}"]`).click();
|
||||
await expect(page).toHaveURL(new RegExp(`${route}$`));
|
||||
await expect(page.locator('[data-source-link]')).toHaveAttribute('href',
|
||||
new RegExp(`/docs/agent_integration${prefix ? '' : '_zh'}.md$`));
|
||||
await expect(page.locator('.docs-article h2')).toHaveCount(6);
|
||||
const block = page.locator('.docs-article pre').first();
|
||||
const expected = await block.locator('code').innerText();
|
||||
await block.locator('button').click();
|
||||
expect((await page.evaluate(() => navigator.clipboard.readText())).trim()).toBe(expected.trim());
|
||||
for (const heading of await page.locator('.docs-article h2').all()) {
|
||||
await heading.scrollIntoViewIfNeeded();
|
||||
expect(await page.evaluate(() => document.documentElement.scrollWidth - innerWidth)).toBeLessThanOrEqual(1);
|
||||
}
|
||||
const token = page.locator('.docs-article p code').filter({ hasText: 'verbose_json' }).first();
|
||||
await expect(token).toBeVisible();
|
||||
expect((await token.boundingBox())!.height).toBeLessThan(40);
|
||||
const peer = `${prefix ? '' : '/en'}/docs/agent-integration.html`;
|
||||
await page.locator(`.docs-article a[href="${peer}"]`).click();
|
||||
await expect(page).toHaveURL(new RegExp(`${peer}$`));
|
||||
expect(errors).toEqual([]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
from pathlib import Path
|
||||
import ast
|
||||
import json
|
||||
import re
|
||||
import shlex
|
||||
import sys
|
||||
from urllib.parse import unquote
|
||||
from urllib.parse import unquote, urlsplit
|
||||
|
||||
from bs4 import BeautifulSoup
|
||||
import pytest
|
||||
@@ -121,3 +124,88 @@ def test_github_pages_hubs_have_task_navigation_and_search_handoff():
|
||||
assert soup.select_one('a[href$="moss-transcribe-diarize.html"]')
|
||||
assert soup.select_one('a[href$="api.html"]')
|
||||
assert '170x' not in soup.get_text()
|
||||
|
||||
|
||||
@pytest.mark.parametrize('language, prefix, suffix, peer', [
|
||||
('zh', '', '_zh', '/en/docs/agent-integration.html'),
|
||||
('en', 'en/', '', '/docs/agent-integration.html'),
|
||||
])
|
||||
def test_agent_guide_has_shared_sources_discovery_and_six_sections(output, language, prefix, suffix, peer):
|
||||
route = '/' + prefix + 'docs/agent-integration.html'
|
||||
path = output / route.lstrip('/')
|
||||
assert path.is_file(), route
|
||||
page = BeautifulSoup(path.read_text(), 'html.parser')
|
||||
article = page.select_one('.docs-article')
|
||||
assert len(article.select('h2')) == 6
|
||||
assert page.select_one('[data-source-link]')['href'].endswith(f'/docs/agent_integration{suffix}.md')
|
||||
links = {a['href'] for a in article.select('a[href]')}
|
||||
assert peer in links
|
||||
routes = {urlsplit(link)._replace(fragment='').geturl() for link in links}
|
||||
for slug in ('http-server', 'workflows', 'javascript', 'speaker-emotion', 'model-selection', 'security'):
|
||||
assert '/' + prefix + 'docs/' + slug + '.html' in routes
|
||||
index = BeautifulSoup((output / prefix / 'docs/index.html').read_text(), 'html.parser')
|
||||
assert index.select_one(f'a[href="{route}"]')
|
||||
search = json.loads((output / f'search-{language}.json').read_text())
|
||||
assert any(row['url'] == route and 'Agent' in row['title'] for row in search)
|
||||
text = article.get_text(' ', strip=True)
|
||||
for marker in ('127.0.0.1', '/v1/audio/transcriptions', '/v1/models', '/health',
|
||||
'verbose_json', 'transcribe_audio', 'FUNASR_DEVICE',
|
||||
'FUNASR_MODEL', 'sentence_info', '--lang', '--segment-mode', '60000'):
|
||||
assert marker in text, (language, marker)
|
||||
assert '31-language LLM-based ASR with timestamps' not in text
|
||||
assert '31 语种 LLM-based ASR,支持时间戳' not in text
|
||||
|
||||
|
||||
def test_agent_recipes_are_bilingual_and_use_existing_script_options():
|
||||
root = SITE_ROOT.parents[1]
|
||||
recipes = []
|
||||
for suffix in ('', '_zh'):
|
||||
path = root / f'docs/agent_integration{suffix}.md'
|
||||
assert path.is_file(), path
|
||||
source = path.read_text()
|
||||
blocks = re.findall(r'^```(\w+)\n(.*?)^```', source, re.M | re.S)
|
||||
commands = []
|
||||
structured = []
|
||||
for language, code in blocks:
|
||||
if language == 'python':
|
||||
tree = ast.parse(code)
|
||||
compile(tree, str(path), 'exec')
|
||||
assert any(isinstance(node, ast.With) for node in ast.walk(tree))
|
||||
structured.append((language, ast.dump(tree)))
|
||||
elif language == 'json':
|
||||
data = json.loads(code)
|
||||
structured.append((language, data))
|
||||
config = data['mcpServers']['funasr']
|
||||
assert config['command'].endswith('/bin/python')
|
||||
assert config['args'][0].endswith('/examples/mcp_server/funasr_mcp.py')
|
||||
assert config['env']['FUNASR_DEVICE'] == 'cpu'
|
||||
elif language == 'bash':
|
||||
commands.extend(tokens for line in code.replace('\\\n', ' ').splitlines()
|
||||
if (tokens := shlex.split(line, comments=True)))
|
||||
recipes.append({'shell': commands, 'structured': structured})
|
||||
scripts = []
|
||||
for command in commands:
|
||||
if command[0] == 'funasr-server':
|
||||
assert command[command.index('--host') + 1] == '127.0.0.1'
|
||||
assert command[command.index('--model') + 1] == 'sensevoice'
|
||||
script = root / 'funasr/bin/server.py'
|
||||
elif command[0] == 'python' and command[1].startswith('examples/'):
|
||||
script = root / command[1]
|
||||
scripts.append(command[1])
|
||||
else:
|
||||
continue
|
||||
assert script.is_file(), script
|
||||
tree = ast.parse(script.read_text())
|
||||
options = {arg.value for node in ast.walk(tree)
|
||||
if isinstance(node, ast.Call) and isinstance(node.func, ast.Attribute)
|
||||
and node.func.attr == 'add_argument' for arg in node.args
|
||||
if isinstance(arg, ast.Constant) and isinstance(arg.value, str)}
|
||||
for token in command[1:]:
|
||||
if token.startswith('--'):
|
||||
assert token in options, (script, token)
|
||||
if script.name == 'funasr_input.py':
|
||||
assert '--lang' not in command
|
||||
assert set(scripts) == {'examples/mcp_server/funasr_mcp.py',
|
||||
'examples/voice_input/funasr_input.py',
|
||||
'examples/subtitle/generate_subtitle.py'}
|
||||
assert recipes[0] == recipes[1]
|
||||
|
||||
@@ -19,6 +19,8 @@ OLD_IDS = {
|
||||
'moss-transcribe-diarize.html': 'contract server runtimes evidence boundaries',
|
||||
'vllm.html': 'overview install offline streaming-sdk websocket dynamic-vad performance api-reference faq',
|
||||
'deployment-matrix.html': 'matrix choices runtime-diagnostics checklist help',
|
||||
# Independently inventoried from public EN/ZH pages on 2026-09-07.
|
||||
'agent.html': 'server sdk workflows mcp voice subtitle',
|
||||
}
|
||||
PAGES = [prefix + name for name in OLD_IDS for prefix in ('', 'zh/')]
|
||||
|
||||
@@ -138,6 +140,17 @@ def test_insertion_is_idempotent_and_keeps_existing_anchors():
|
||||
assert soup.find(id='existing').name == 'a'
|
||||
|
||||
|
||||
@pytest.mark.parametrize('page, targets', [
|
||||
('agent.html', ['http-server', 'sdk-and-curl', 'workflow-integrations',
|
||||
'mcp-server', 'desktop-voice-input', 'subtitle-generation']),
|
||||
('zh/agent.html', ['http-服务', 'sdk-与-curl', '工作流集成',
|
||||
'mcp-服务', '桌面语音输入', '字幕生成']),
|
||||
])
|
||||
def test_agent_legacy_fragments_keep_their_topics(page, targets):
|
||||
pages = json.loads((SITE / 'data/legacy_doc_anchors.json').read_text())['pages']
|
||||
assert pages[page] == dict(zip('server sdk workflows mcp voice subtitle'.split(), targets))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('heading', ('<h2 id="different">Topic</h2>', '<p id="topic">Not a heading</p>'))
|
||||
def test_missing_or_nonheading_target_fails_explicitly(heading):
|
||||
from export_docs import insert_legacy_anchors
|
||||
|
||||
Reference in New Issue
Block a user