From 6165c199907938e58384d2f318ed277a7efad2c2 Mon Sep 17 00:00:00 2001 From: zhifu gao Date: Mon, 25 May 2026 18:37:40 +0800 Subject: [PATCH] demo: add Gradio OpenAI API browser demo Add an optional Gradio browser UI and docs for the FunASR OpenAI-compatible API. --- README.md | 2 +- README_zh.md | 2 +- docs/deployment_matrix.md | 2 +- docs/deployment_matrix_zh.md | 2 +- docs/use_case_showcase.md | 3 +- docs/use_case_showcase_zh.md | 3 +- examples/openai_api/CLIENTS.md | 2 +- examples/openai_api/GRADIO.md | 62 ++++++++++++ examples/openai_api/GRADIO_zh.md | 62 ++++++++++++ examples/openai_api/README.md | 15 ++- examples/openai_api/README_zh.md | 15 ++- examples/openai_api/gradio_app.py | 157 ++++++++++++++++++++++++++++++ 12 files changed, 316 insertions(+), 11 deletions(-) create mode 100644 examples/openai_api/GRADIO.md create mode 100644 examples/openai_api/GRADIO_zh.md create mode 100644 examples/openai_api/gradio_app.py diff --git a/README.md b/README.md index 7181c6325..514bc9b9e 100644 --- a/README.md +++ b/README.md @@ -198,7 +198,7 @@ curl http://localhost:8000/v1/audio/transcriptions \ docker pull registry.cn-hangzhou.aliyuncs.com/funasr_repo/funasr:funasr-runtime-sdk-online-cpu-0.1.12 ``` -[OpenAI API example →](./examples/openai_api/) · [Client recipes →](./examples/openai_api/CLIENTS.md) · [JavaScript/TypeScript recipes →](./examples/openai_api/JAVASCRIPT.md) · [Kubernetes template →](./examples/openai_api/kubernetes/) · [Workflow recipes →](./examples/openai_api/WORKFLOWS.md) · [Postman collection →](./examples/openai_api/POSTMAN.md) · [OpenAPI spec →](./examples/openai_api/OPENAPI.md) · [Deployment matrix →](./docs/deployment_matrix.md) · [Deployment docs →](./runtime/readme.md) · [Agent integration →](https://modelscope.github.io/FunASR/agent.html) +[OpenAI API example →](./examples/openai_api/) · [Gradio demo →](./examples/openai_api/GRADIO.md) · [Client recipes →](./examples/openai_api/CLIENTS.md) · [JavaScript/TypeScript recipes →](./examples/openai_api/JAVASCRIPT.md) · [Kubernetes template →](./examples/openai_api/kubernetes/) · [Workflow recipes →](./examples/openai_api/WORKFLOWS.md) · [Postman collection →](./examples/openai_api/POSTMAN.md) · [OpenAPI spec →](./examples/openai_api/OPENAPI.md) · [Deployment matrix →](./docs/deployment_matrix.md) · [Deployment docs →](./runtime/readme.md) · [Agent integration →](https://modelscope.github.io/FunASR/agent.html) --- diff --git a/README_zh.md b/README_zh.md index 543f845c6..14e91c0a7 100644 --- a/README_zh.md +++ b/README_zh.md @@ -197,7 +197,7 @@ curl http://localhost:8000/v1/audio/transcriptions \ docker pull registry.cn-hangzhou.aliyuncs.com/funasr_repo/funasr:funasr-runtime-sdk-online-cpu-0.1.12 ``` -[OpenAI API 示例 →](./examples/openai_api/README_zh.md) · [客户端配方 →](./examples/openai_api/CLIENTS.md) · [JavaScript/TypeScript 配方 →](./examples/openai_api/JAVASCRIPT_zh.md) · [Kubernetes 模板 →](./examples/openai_api/kubernetes/README_zh.md) · [工作流配方 →](./examples/openai_api/WORKFLOWS_zh.md) · [Postman 集合 →](./examples/openai_api/POSTMAN_zh.md) · [OpenAPI 规范 →](./examples/openai_api/OPENAPI_zh.md) · [部署选型 →](./docs/deployment_matrix_zh.md) · [部署文档 →](./runtime/readme_cn.md) · [Agent 集成 →](https://modelscope.github.io/FunASR/agent.html) +[OpenAI API 示例 →](./examples/openai_api/README_zh.md) · [Gradio Demo →](./examples/openai_api/GRADIO_zh.md) · [客户端配方 →](./examples/openai_api/CLIENTS.md) · [JavaScript/TypeScript 配方 →](./examples/openai_api/JAVASCRIPT_zh.md) · [Kubernetes 模板 →](./examples/openai_api/kubernetes/README_zh.md) · [工作流配方 →](./examples/openai_api/WORKFLOWS_zh.md) · [Postman 集合 →](./examples/openai_api/POSTMAN_zh.md) · [OpenAPI 规范 →](./examples/openai_api/OPENAPI_zh.md) · [部署选型 →](./docs/deployment_matrix_zh.md) · [部署文档 →](./runtime/readme_cn.md) · [Agent 集成 →](https://modelscope.github.io/FunASR/agent.html) --- diff --git a/docs/deployment_matrix.md b/docs/deployment_matrix.md index 4ba579eee..39039371d 100644 --- a/docs/deployment_matrix.md +++ b/docs/deployment_matrix.md @@ -25,7 +25,7 @@ Use the Python API from the README. It is the shortest route for validating inst ### I want a local replacement for cloud transcription -Use the OpenAI-compatible API. It exposes `/v1/audio/transcriptions`, `/v1/models`, `/health`, and Swagger docs. Start with `sensevoice`, run `examples/openai_api/smoke_test.sh` or `examples/openai_api/smoke_test.py`, then connect existing SDK or HTTP clients using [client recipes](../examples/openai_api/CLIENTS.md) and [JavaScript/TypeScript recipes](../examples/openai_api/JAVASCRIPT.md). For Dify, n8n, HTTP nodes, or webhook workers, follow the [workflow recipes](../examples/openai_api/WORKFLOWS.md). For API gateways, developer portals, and schema-driven imports, use the [OpenAPI spec](../examples/openai_api/OPENAPI.md). +Use the OpenAI-compatible API. It exposes `/v1/audio/transcriptions`, `/v1/models`, `/health`, and Swagger docs. Start with `sensevoice`, run `examples/openai_api/smoke_test.sh` or `examples/openai_api/smoke_test.py`, then connect existing SDK or HTTP clients using [client recipes](../examples/openai_api/CLIENTS.md) and [JavaScript/TypeScript recipes](../examples/openai_api/JAVASCRIPT.md). For browser upload or microphone demos, use the [Gradio browser demo](../examples/openai_api/GRADIO.md). For Dify, n8n, HTTP nodes, or webhook workers, follow the [workflow recipes](../examples/openai_api/WORKFLOWS.md). For API gateways, developer portals, and schema-driven imports, use the [OpenAPI spec](../examples/openai_api/OPENAPI.md). ### I want a repeatable container demo diff --git a/docs/deployment_matrix_zh.md b/docs/deployment_matrix_zh.md index a09ece909..0d68678b2 100644 --- a/docs/deployment_matrix_zh.md +++ b/docs/deployment_matrix_zh.md @@ -25,7 +25,7 @@ ### 我想替代云端转写服务 -使用 OpenAI 兼容 API。它提供 `/v1/audio/transcriptions`、`/v1/models`、`/health` 和 Swagger docs。先用 `sensevoice` 跑通 `examples/openai_api/smoke_test.sh` 或 `examples/openai_api/smoke_test.py`,再根据 [客户端配方](../examples/openai_api/CLIENTS.md) 和 [JavaScript/TypeScript 配方](../examples/openai_api/JAVASCRIPT_zh.md) 接入 SDK 或 HTTP 客户端。Dify、n8n、HTTP 节点或 webhook worker 可参考 [工作流配方](../examples/openai_api/WORKFLOWS_zh.md)。API 网关、开发者门户或按 schema 导入时可使用 [OpenAPI 规范](../examples/openai_api/OPENAPI_zh.md)。 +使用 OpenAI 兼容 API。它提供 `/v1/audio/transcriptions`、`/v1/models`、`/health` 和 Swagger docs。先用 `sensevoice` 跑通 `examples/openai_api/smoke_test.sh` 或 `examples/openai_api/smoke_test.py`,再根据 [客户端配方](../examples/openai_api/CLIENTS.md) 和 [JavaScript/TypeScript 配方](../examples/openai_api/JAVASCRIPT_zh.md) 接入 SDK 或 HTTP 客户端。浏览器上传或麦克风 demo 可使用 [Gradio 浏览器 Demo](../examples/openai_api/GRADIO_zh.md)。Dify、n8n、HTTP 节点或 webhook worker 可参考 [工作流配方](../examples/openai_api/WORKFLOWS_zh.md)。API 网关、开发者门户或按 schema 导入时可使用 [OpenAPI 规范](../examples/openai_api/OPENAPI_zh.md)。 ### 我想要可复现的容器 demo diff --git a/docs/use_case_showcase.md b/docs/use_case_showcase.md index 802a99a5d..8d75baf51 100644 --- a/docs/use_case_showcase.md +++ b/docs/use_case_showcase.md @@ -9,7 +9,7 @@ FunASR is useful far beyond a single offline transcription command. This page co | Transcribe one file locally | [README quick start](../README.md#quick-start) and [model selection guide](./model_selection.md) | Verify install, model choice, and model download in minutes. | | Compare accuracy and speed | [Benchmark report](https://modelscope.github.io/FunASR/benchmark.html) | Reproduce the 184-file long-audio benchmark before choosing a model. | | Migrate from Whisper/cloud ASR | [Migration guide](./migration_from_whisper.md) | Map existing pipelines to FunASR, benchmark representative audio, and plan a safe rollout. | -| Build a private speech API | [OpenAI-compatible API example](../examples/openai_api/), [client recipes](../examples/openai_api/CLIENTS.md), [JavaScript/TypeScript recipes](../examples/openai_api/JAVASCRIPT.md), and [workflow recipes](../examples/openai_api/WORKFLOWS.md) | Reuse LangChain, Dify, n8n, AutoGen, and other OpenAI-style clients without sending audio to a cloud ASR provider. | +| Build a private speech API | [OpenAI-compatible API example](../examples/openai_api/), [Gradio browser demo](../examples/openai_api/GRADIO.md), [client recipes](../examples/openai_api/CLIENTS.md), [JavaScript/TypeScript recipes](../examples/openai_api/JAVASCRIPT.md), and [workflow recipes](../examples/openai_api/WORKFLOWS.md) | Reuse LangChain, Dify, n8n, AutoGen, and other OpenAI-style clients without sending audio to a cloud ASR provider. | | Add speech input to agents | [MCP server](../examples/mcp_server/) and [voice input](../examples/voice_input/) | Connect local ASR to Claude, Cursor, and desktop agent workflows. | | Choose a deployment path | [Deployment matrix](./deployment_matrix.md) | Compare Python API, OpenAI API, Docker Compose, Kubernetes, WebSocket, vLLM, MCP, batch, subtitles, and Triton. | | Serve streaming ASR | [Runtime service docs](../runtime/readme.md) | Run WebSocket or service-mode ASR for live captioning and call-center style workloads. | @@ -38,6 +38,7 @@ curl http://localhost:8000/v1/audio/transcriptions \ Recommended next steps: - Run the [OpenAI-compatible API smoke test](../examples/openai_api/smoke_test.sh) or the cross-platform [Python smoke test](../examples/openai_api/smoke_test.py). +- For browser upload or microphone demos, start from the [Gradio browser demo](../examples/openai_api/GRADIO.md). - For Node.js or Next.js services, start from the [JavaScript/TypeScript recipes](../examples/openai_api/JAVASCRIPT.md). - For cluster services, start from the [Kubernetes deployment template](../examples/openai_api/kubernetes/). - Add authentication and network controls at your service boundary. diff --git a/docs/use_case_showcase_zh.md b/docs/use_case_showcase_zh.md index d741ec6ea..d060c507d 100644 --- a/docs/use_case_showcase_zh.md +++ b/docs/use_case_showcase_zh.md @@ -9,7 +9,7 @@ FunASR 不只是一个离线转写命令。这个页面把常见的评测、部 | 本地转写一个文件 | [README 快速开始](../README_zh.md#快速开始) 和 [模型选择指南](./model_selection_zh.md) | 几分钟内验证安装、模型选择、模型下载和首次推理。 | | 对比准确率和速度 | [性能评测报告](https://modelscope.github.io/FunASR/zh/benchmark.html) | 选型前先查看 184 条长音频评测结果。 | | 从 Whisper/云端 ASR 迁移 | [迁移指南](./migration_from_whisper_zh.md) | 将现有流水线映射到 FunASR,用代表性音频评测并规划安全上线。 | -| 搭建私有语音 API | [OpenAI 兼容 API 示例](../examples/openai_api/README_zh.md)、[客户端配方](../examples/openai_api/CLIENTS.md)、[JavaScript/TypeScript 配方](../examples/openai_api/JAVASCRIPT_zh.md) 和 [工作流配方](../examples/openai_api/WORKFLOWS_zh.md) | 复用 LangChain、Dify、n8n、AutoGen 等 OpenAI 风格客户端,音频不出内网。 | +| 搭建私有语音 API | [OpenAI 兼容 API 示例](../examples/openai_api/README_zh.md)、[Gradio 浏览器 Demo](../examples/openai_api/GRADIO_zh.md)、[客户端配方](../examples/openai_api/CLIENTS.md)、[JavaScript/TypeScript 配方](../examples/openai_api/JAVASCRIPT_zh.md) 和 [工作流配方](../examples/openai_api/WORKFLOWS_zh.md) | 复用 LangChain、Dify、n8n、AutoGen 等 OpenAI 风格客户端,音频不出内网。 | | 给 Agent 增加语音输入 | [MCP 服务](../examples/mcp_server/) 和 [语音输入示例](../examples/voice_input/) | 将本地 ASR 接入 Claude、Cursor 和桌面 Agent 工作流。 | | 选择部署路径 | [部署选型表](./deployment_matrix_zh.md) | 对比 Python API、OpenAI API、Docker Compose、Kubernetes、WebSocket、vLLM、MCP、批处理、字幕和 Triton。 | | 部署流式 ASR | [Runtime 服务文档](../runtime/readme_cn.md) | 面向实时字幕、客服、会议等低延迟场景。 | @@ -38,6 +38,7 @@ curl http://localhost:8000/v1/audio/transcriptions \ 建议下一步: - 运行 [OpenAI 兼容 API smoke test](../examples/openai_api/smoke_test.sh) 或跨平台 [Python smoke test](../examples/openai_api/smoke_test.py)。 +- 浏览器上传或麦克风 demo 可从 [Gradio 浏览器 Demo](../examples/openai_api/GRADIO_zh.md) 开始。 - Node.js 或 Next.js 服务可从 [JavaScript/TypeScript 配方](../examples/openai_api/JAVASCRIPT_zh.md) 开始。 - 集群内服务可从 [Kubernetes 部署模板](../examples/openai_api/kubernetes/README_zh.md) 开始。 - 在服务边界增加鉴权、限流和网络访问控制。 diff --git a/examples/openai_api/CLIENTS.md b/examples/openai_api/CLIENTS.md index 24a21cbd2..ef6ba295b 100644 --- a/examples/openai_api/CLIENTS.md +++ b/examples/openai_api/CLIENTS.md @@ -1,6 +1,6 @@ # Client Recipes for the FunASR OpenAI-Compatible API -Use this page when `funasr-server` is already running and you want to connect an existing application, agent tool, or workflow engine to local speech recognition. For JavaScript, TypeScript, and Next.js examples, see the [JavaScript/TypeScript recipes](JAVASCRIPT.md) or [Chinese JavaScript/TypeScript recipes](JAVASCRIPT_zh.md). For Dify, n8n, HTTP nodes, and webhook workers, see the [workflow recipes](WORKFLOWS.md) or [Chinese workflow recipes](WORKFLOWS_zh.md). For no-code API smoke tests, import the [Postman collection](POSTMAN.md). For schema-driven imports or client generation, use the [OpenAPI spec](OPENAPI.md). +Use this page when `funasr-server` is already running and you want to connect an existing application, agent tool, or workflow engine to local speech recognition. For JavaScript, TypeScript, and Next.js examples, see the [JavaScript/TypeScript recipes](JAVASCRIPT.md) or [Chinese JavaScript/TypeScript recipes](JAVASCRIPT_zh.md). For Dify, n8n, HTTP nodes, and webhook workers, see the [workflow recipes](WORKFLOWS.md) or [Chinese workflow recipes](WORKFLOWS_zh.md). For browser upload or microphone demos, use the [Gradio browser demo](GRADIO.md). For no-code API smoke tests, import the [Postman collection](POSTMAN.md). For schema-driven imports or client generation, use the [OpenAPI spec](OPENAPI.md). ## Preflight diff --git a/examples/openai_api/GRADIO.md b/examples/openai_api/GRADIO.md new file mode 100644 index 000000000..f8146398b --- /dev/null +++ b/examples/openai_api/GRADIO.md @@ -0,0 +1,62 @@ +# Gradio Browser Demo for the FunASR OpenAI-Compatible API + +Use this demo when you want a browser UI for uploading or recording audio while the FunASR OpenAI-compatible API server runs locally, in Docker, or behind a private Kubernetes service. + +The Gradio app does not load FunASR models itself. It calls the same `/health`, `/v1/models`, and `/v1/audio/transcriptions` endpoints used by the smoke tests, SDK recipes, Postman collection, and OpenAPI spec. + +## 1. Start the API server + +From `examples/openai_api`: + +```bash +pip install funasr fastapi uvicorn python-multipart +python server.py --model sensevoice --device cuda --port 8000 +``` + +For a portable CPU check, use `--device cpu`. For Docker Compose or Kubernetes, keep the service private and expose it locally with the documented port mapping or `kubectl port-forward`. + +## 2. Install and launch the browser UI + +In another terminal: + +```bash +pip install gradio +python gradio_app.py --base-url http://localhost:8000 +``` + +Open the printed local URL, upload or record an audio file, choose a model alias, and click **Transcribe**. + +## 3. Verify the backend first + +The UI has a **Check service** button. You can run the same check in a terminal: + +```bash +python smoke_test.py --base-url http://localhost:8000 +curl http://localhost:8000/v1/models +``` + +If the API server is remote, set the reachable URL explicitly: + +```bash +python gradio_app.py --base-url http://funasr-api.speech.svc.cluster.local:8000 +``` + +For OpenAI SDK clients, remember that SDK base URLs include `/v1`; this Gradio demo expects the direct service base URL without `/v1`. + +## Model aliases + +| Alias | Good first use | +|---|---| +| `sensevoice` | Fast multilingual private transcription and agent voice input. | +| `paraformer` | Mandarin-oriented production transcription. | +| `paraformer-en` | English-only compatibility checks. | +| `fun-asr-nano` | LLM-based ASR and vLLM experiments. | + +See the [model selection guide](../../docs/model_selection.md) for a deeper comparison. + +## Production notes + +- Treat the Gradio app as a demo or internal operator UI, not a public production frontend. +- Add authentication, TLS, upload-size limits, and rate limits before exposing any audio upload UI outside a trusted network. +- Keep browser uploads close to your backend; do not send private audio to an unauthenticated public endpoint. +- Log model alias, audio duration, latency, response format, and error text when debugging. diff --git a/examples/openai_api/GRADIO_zh.md b/examples/openai_api/GRADIO_zh.md new file mode 100644 index 000000000..6d8040ada --- /dev/null +++ b/examples/openai_api/GRADIO_zh.md @@ -0,0 +1,62 @@ +# FunASR OpenAI 兼容 API Gradio 浏览器 Demo + +当 FunASR OpenAI 兼容 API 已经在本地、Docker 或私有 Kubernetes 服务中运行,而你想用浏览器上传或录制音频时,可以使用这个 Gradio demo。 + +Gradio app 本身不加载 FunASR 模型。它调用和 smoke test、SDK 配方、Postman 集合、OpenAPI 规范相同的 `/health`、`/v1/models` 和 `/v1/audio/transcriptions` 接口。 + +## 1. 启动 API 服务 + +在 `examples/openai_api` 目录中执行: + +```bash +pip install funasr fastapi uvicorn python-multipart +python server.py --model sensevoice --device cuda --port 8000 +``` + +便携 CPU 验证可以使用 `--device cpu`。如果使用 Docker Compose 或 Kubernetes,请保持服务私有,并通过文档中的端口映射或 `kubectl port-forward` 暴露到本机验证。 + +## 2. 安装并启动浏览器 UI + +在另一个终端执行: + +```bash +pip install gradio +python gradio_app.py --base-url http://localhost:8000 +``` + +打开命令行输出的本地 URL,上传或录制音频,选择模型 alias,然后点击 **Transcribe**。 + +## 3. 先验证后端服务 + +UI 中有 **Check service** 按钮。你也可以在终端运行相同检查: + +```bash +python smoke_test.py --base-url http://localhost:8000 +curl http://localhost:8000/v1/models +``` + +如果 API 服务在远端,请显式设置可访问的地址: + +```bash +python gradio_app.py --base-url http://funasr-api.speech.svc.cluster.local:8000 +``` + +OpenAI SDK 的 base URL 需要包含 `/v1`;这个 Gradio demo 使用的是不带 `/v1` 的直接服务 base URL。 + +## 模型别名 + +| Alias | 适合场景 | +|---|---| +| `sensevoice` | 快速多语种私有转写和 Agent 语音输入。 | +| `paraformer` | 中文生产转写。 | +| `paraformer-en` | 英文兼容性检查。 | +| `fun-asr-nano` | LLM-based ASR 和 vLLM 实验。 | + +更完整的对比见 [模型选择指南](../../docs/model_selection_zh.md)。 + +## 生产注意事项 + +- Gradio app 适合作为 demo 或内部操作界面,不建议直接作为公网生产前端。 +- 任何音频上传 UI 对可信网络外开放前,都需要先增加鉴权、TLS、上传大小限制和限流。 +- 浏览器上传应尽量靠近你的后端服务,不要把私有音频发送到未鉴权的公网端点。 +- 排查问题时记录模型 alias、音频时长、延迟、响应格式和错误文本。 diff --git a/examples/openai_api/README.md b/examples/openai_api/README.md index 3a250c3ee..ed9207237 100644 --- a/examples/openai_api/README.md +++ b/examples/openai_api/README.md @@ -13,7 +13,7 @@ python server.py --model sensevoice --device cuda --port 8000 Server starts in ~20s (model loading). Health check: `GET /health` -Need copy-paste integration snippets for Python SDK, JavaScript/TypeScript, HTTP clients, agent tools, Postman, OpenAPI imports, Kubernetes deployment, or Dify/n8n-style workflows? See [Client recipes](CLIENTS.md), [JavaScript/TypeScript recipes](JAVASCRIPT.md), [workflow recipes](WORKFLOWS.md), the [Chinese workflow recipes](WORKFLOWS_zh.md), the [Postman collection](POSTMAN.md), the [OpenAPI spec](OPENAPI.md), and the [Kubernetes deployment template](kubernetes/README.md). +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), and the [Kubernetes deployment template](kubernetes/README.md). ### End-to-end smoke test @@ -36,6 +36,17 @@ curl http://localhost:8000/v1/audio/transcriptions \ -F response_format=verbose_json ``` +## Browser demo with Gradio + +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 gradio_app.py --base-url http://localhost:8000 +``` + +The browser demo calls the same OpenAI-compatible API endpoints as the smoke tests. See [Gradio browser demo](GRADIO.md) for Docker, Kubernetes, and production notes. + ## Usage with OpenAI SDK (Python) ```python @@ -91,7 +102,7 @@ curl http://localhost:8000/v1/audio/transcriptions \ | `/health` | GET | Health check + loaded models | | `/docs` | GET | Interactive API documentation (Swagger) | -Prefer no-code API checks? Import the [Postman collection](POSTMAN.md) and run health, model-list, and transcription requests from Postman. For API gateways, developer portals, or client generation, use the [OpenAPI spec](OPENAPI.md). +Prefer no-code API checks? Use the [Gradio browser demo](GRADIO.md) for local upload or microphone testing, or import the [Postman collection](POSTMAN.md) and run health, model-list, and transcription requests from Postman. For API gateways, developer portals, or client generation, use the [OpenAPI spec](OPENAPI.md). ## Agent Framework Integration diff --git a/examples/openai_api/README_zh.md b/examples/openai_api/README_zh.md index 28af9a506..f19c8fbb9 100644 --- a/examples/openai_api/README_zh.md +++ b/examples/openai_api/README_zh.md @@ -13,7 +13,7 @@ python server.py --model sensevoice --device cuda --port 8000 服务通常会在模型加载后启动。健康检查:`GET /health`。 -需要直接复制的接入示例?可以继续查看 [客户端配方](CLIENTS.md)、[JavaScript/TypeScript 配方](JAVASCRIPT_zh.md)、[工作流配方](WORKFLOWS_zh.md)、[Postman 集合](POSTMAN_zh.md)、[OpenAPI 规范](OPENAPI_zh.md) 和 [Kubernetes 部署模板](kubernetes/README_zh.md)。 +需要直接复制的接入示例?可以继续查看 [客户端配方](CLIENTS.md)、[JavaScript/TypeScript 配方](JAVASCRIPT_zh.md)、[Gradio 浏览器 Demo](GRADIO_zh.md)、[工作流配方](WORKFLOWS_zh.md)、[Postman 集合](POSTMAN_zh.md)、[OpenAPI 规范](OPENAPI_zh.md) 和 [Kubernetes 部署模板](kubernetes/README_zh.md)。 ### 端到端 smoke test @@ -36,6 +36,17 @@ curl http://localhost:8000/v1/audio/transcriptions \ -F response_format=verbose_json ``` +## Gradio 浏览器 Demo + +如果希望用本地浏览器上传音频或测试麦克风,先启动 API 服务,再运行可选 Gradio 前端: + +```bash +pip install gradio +python gradio_app.py --base-url http://localhost:8000 +``` + +这个浏览器 demo 调用的就是 smoke test 使用的 OpenAI 兼容 API 端点。Docker、Kubernetes 和生产注意事项见 [Gradio 浏览器 Demo](GRADIO_zh.md)。 + ## 使用 OpenAI SDK ```python @@ -88,7 +99,7 @@ curl http://localhost:8000/v1/audio/transcriptions \ | `/health` | GET | 健康检查、已加载模型和可用模型 | | `/docs` | GET | FastAPI Swagger 文档 | -不想写代码验证接口时,可以导入 [Postman 集合](POSTMAN_zh.md)。如果要接入 API 网关、开发者门户或生成内部客户端,可以使用 [OpenAPI 规范](OPENAPI_zh.md)。 +不想写代码验证接口时,可以使用 [Gradio 浏览器 Demo](GRADIO_zh.md) 做本地上传或麦克风测试,也可以导入 [Postman 集合](POSTMAN_zh.md)。如果要接入 API 网关、开发者门户或生成内部客户端,可以使用 [OpenAPI 规范](OPENAPI_zh.md)。 ## Agent 与低代码工作流 diff --git a/examples/openai_api/gradio_app.py b/examples/openai_api/gradio_app.py new file mode 100644 index 000000000..a1978861b --- /dev/null +++ b/examples/openai_api/gradio_app.py @@ -0,0 +1,157 @@ +#!/usr/bin/env python3 +"""Browser demo for the FunASR OpenAI-compatible API.""" + +from __future__ import annotations + +import argparse +import json +import mimetypes +import os +from pathlib import Path +import urllib.error +import urllib.request +import uuid + +DEFAULT_BASE_URL = "http://localhost:8000" +DEFAULT_MODEL = "sensevoice" + + +def request_json(url: str, timeout: float) -> dict: + request = urllib.request.Request(url, headers={"Accept": "application/json"}) + with urllib.request.urlopen(request, timeout=timeout) as response: + return json.loads(response.read().decode("utf-8")) + + +def multipart_body(audio_path: Path, model: str, response_format: str) -> tuple[bytes, str]: + boundary = f"----funasr-gradio-{uuid.uuid4().hex}" + content_type = mimetypes.guess_type(str(audio_path))[0] or "application/octet-stream" + parts: list[bytes] = [] + + def add_text(name: str, value: str) -> None: + parts.append( + ( + f"--{boundary}\r\n" + f"Content-Disposition: form-data; name=\"{name}\"\r\n\r\n" + f"{value}\r\n" + ).encode("utf-8") + ) + + parts.append( + ( + f"--{boundary}\r\n" + f"Content-Disposition: form-data; name=\"file\"; filename=\"{audio_path.name}\"\r\n" + f"Content-Type: {content_type}\r\n\r\n" + ).encode("utf-8") + ) + parts.append(audio_path.read_bytes()) + parts.append(b"\r\n") + add_text("model", model) + add_text("response_format", response_format) + parts.append(f"--{boundary}--\r\n".encode("utf-8")) + return b"".join(parts), boundary + + +def transcribe_audio(base_url: str, audio_path: str | None, model: str, response_format: str, timeout: float) -> tuple[str, str]: + if not audio_path: + return "", "Upload or record an audio file first." + + base_url = base_url.rstrip("/") + path = Path(audio_path) + body, boundary = multipart_body(path, model, response_format) + request = urllib.request.Request( + f"{base_url}/v1/audio/transcriptions", + data=body, + method="POST", + headers={ + "Accept": "application/json", + "Content-Type": f"multipart/form-data; boundary={boundary}", + "Content-Length": str(len(body)), + }, + ) + with urllib.request.urlopen(request, timeout=timeout) as response: + payload = json.loads(response.read().decode("utf-8")) + + text = payload.get("text", "") + return text, json.dumps(payload, ensure_ascii=False, indent=2) + + +def check_service(base_url: str, timeout: float) -> str: + base_url = base_url.rstrip("/") + health = request_json(f"{base_url}/health", timeout) + models = request_json(f"{base_url}/v1/models", timeout) + return json.dumps({"health": health, "models": models}, ensure_ascii=False, indent=2) + + +def safe_transcribe(base_url: str, audio_path: str | None, model: str, response_format: str, timeout: float) -> tuple[str, str]: + try: + return transcribe_audio(base_url, audio_path, model, response_format, timeout) + except urllib.error.HTTPError as error: + detail = error.read().decode("utf-8", errors="replace") + return "", f"HTTP {error.code} from {error.url}: {detail}" + except Exception as error: + return "", f"Transcription failed: {error}" + + +def safe_check(base_url: str, timeout: float) -> str: + try: + return check_service(base_url, timeout) + except urllib.error.HTTPError as error: + detail = error.read().decode("utf-8", errors="replace") + return f"HTTP {error.code} from {error.url}: {detail}" + except Exception as error: + return f"Service check failed: {error}" + + +def build_app(default_base_url: str, default_timeout: float): + try: + import gradio as gr + except ImportError as error: + raise SystemExit("Install Gradio first: pip install gradio") from error + + with gr.Blocks(title="FunASR OpenAI API Demo") as demo: + gr.Markdown("# FunASR OpenAI-Compatible API Demo") + gr.Markdown("Start `python server.py --model sensevoice --device cuda --port 8000`, then upload or record audio here.") + + with gr.Row(): + base_url = gr.Textbox(label="API base URL", value=default_base_url) + model = gr.Dropdown( + label="Model alias", + choices=["sensevoice", "paraformer", "paraformer-en", "fun-asr-nano"], + value=DEFAULT_MODEL, + ) + response_format = gr.Radio(label="Response format", choices=["json", "verbose_json"], value="verbose_json") + timeout = gr.Number(label="Timeout seconds", value=default_timeout, precision=0) + + audio = gr.Audio(label="Audio", sources=["upload", "microphone"], type="filepath") + with gr.Row(): + check_button = gr.Button("Check service") + transcribe_button = gr.Button("Transcribe", variant="primary") + + transcript = gr.Textbox(label="Transcript", lines=6) + raw_json = gr.Code(label="Raw JSON or status", language="json") + + check_button.click(fn=safe_check, inputs=[base_url, timeout], outputs=raw_json) + transcribe_button.click( + fn=safe_transcribe, + inputs=[base_url, audio, model, response_format, timeout], + outputs=[transcript, raw_json], + ) + + return demo + + +def main() -> None: + parser = argparse.ArgumentParser(description="Run a Gradio demo for the FunASR OpenAI-compatible API") + parser.add_argument("--base-url", default=os.getenv("BASE_URL", DEFAULT_BASE_URL), help="FunASR API base URL") + parser.add_argument("--host", default=os.getenv("GRADIO_HOST", "127.0.0.1"), help="Gradio bind host") + parser.add_argument("--port", type=int, default=int(os.getenv("GRADIO_PORT", "7860")), help="Gradio bind port") + parser.add_argument("--timeout", type=float, default=float(os.getenv("TIMEOUT", "300")), help="HTTP timeout in seconds") + parser.add_argument("--share", action="store_true", help="Create a temporary Gradio share link") + args = parser.parse_args() + + app = build_app(args.base_url, args.timeout) + app.launch(server_name=args.host, server_port=args.port, share=args.share) + + +if __name__ == "__main__": + main()