mirror of
https://github.com/modelscope/FunASR.git
synced 2026-10-12 02:19:18 +00:00
fix(website): correct LiteLLM transcription route and guards
Signed-off-by: LauraGPT <[email protected]>
This commit is contained in:
@@ -266,7 +266,7 @@ High-star feature requests and roadmap issues are earlier in the funnel than PRs
|
||||
| `deepset-ai/haystack-core-integrations#3572` FunASR-Haystack Python 3.14 installation | Removes an installation scare in Haystack's integration catalog that can prevent new users from trying FunASR | A current Windows CPython 3.14 resolver check succeeds with `funasr 1.3.14`, `umap-learn 0.5.12`, `pynndescent 0.6.0`, `numba 0.66.0`, and `llvmlite 0.48.0`; the reported `llvmlite 0.36.0` path is therefore more consistent with a stale lock or lagging package index than the current dependency graph. Diagnostic commands and requested lock/index evidence are at https://github.com/deepset-ai/haystack-core-integrations/issues/3572#issuecomment-4965365012. Do not change core packaging until the reporter provides a current failing resolution. |
|
||||
| `crewAIInc/crewAI#5983` FunASR for voice-enabled agents | Routes a 55k-star multi-agent framework toward a provider-neutral voice command path where FunASR/SenseVoice can be a local OpenAI-compatible transcription backend | LauraGPT revived the stale broad request with a concrete `voice.stt.*` config, multipart `/v1/audio/transcriptions` contract, mock endpoint test shape, and agent-command handoff at https://github.com/crewAIInc/crewAI/issues/5983#issuecomment-4901293351. Watch for maintainer direction before opening a code PR. |
|
||||
| `Significant-Gravitas/AutoGPT#13347` FunASR as an open-source STT backend | Keeps the 185k-star AutoGPT voice-input discussion anchored on a generic OpenAI-compatible transcription contract rather than a heavyweight FunASR-only dependency | LauraGPT mapped the existing hard-coded Copilot Whisper route, then opened PR `Significant-Gravitas/AutoGPT#13500` and linked it back at https://github.com/Significant-Gravitas/AutoGPT/issues/13347#issuecomment-4908286807. The PR later merged, so keep this as a completed high-visibility OpenAI-compatible STT contract win rather than an active operator item. |
|
||||
| `modelscope/FunASR#3334/#3350/#3361/#3362/#3363/#3366/#3398` owned conversion and link hygiene | Protects owned website, docs, runtime, model_zoo, and first-run troubleshooting surfaces that already send qualified traffic to the four repositories | Merged on 2026-07-23 and hardened again after the donor-page and troubleshooting reviews. `scripts/check_funasr_website_static.py` now patrols 14 public pages across home, ecosystem, donor, CLI tutorial, llama.cpp landing, llama.cpp blog, and launch-story surfaces. It requires the current `35K+` ecosystem proof, visible donor copy saying donations buy servers and the `www.funasr.com` domain, LiteLLM `custom_openai` routing, `funasr >= 1.3.26` CLI guidance, and `runtime-llamacpp-v0.1.9` download paths, including the Windows Vulkan ZIP, while forbidding stale `16K+`, `1.3.10`, and `runtime-llamacpp-v0.1.1` regressions. PRs #3361-#3363 refreshed high-traffic runtime download commands, package/help metadata, model_zoo Hugging Face links, and verified core ModelScope entries from stale `alibaba-damo-academy` / `damo` paths to current `modelscope/FunASR` / `iic` pages. `modelscope/FunASR#3366` then added README-linked install and deployment FAQs at [`docs/troubleshooting.md`](./troubleshooting.md) and [`docs/troubleshooting_zh.md`](./troubleshooting_zh.md), covering `torch` / `torchaudio`, ModelScope versus Hugging Face downloads, `funasr-server` `/v1/audio/transcriptions`, `WebSocket` realtime checks, `llama.cpp` / `GGUF`, and `Deployment Help` issue details. Post-merge validation passed the static contract suite, docs link guards, FAQ guards, real public website contract runs across all 14 pages, and HTTP 200 checks for updated owned/model links. |
|
||||
| `modelscope/FunASR#3334/#3350/#3361/#3362/#3363/#3366/#3398` owned conversion and link hygiene | Protects owned website, docs, runtime, model_zoo, and first-run troubleshooting surfaces that already send qualified traffic to the four repositories | Historical July 2026 record: these PRs protected 14 public pages, donor usage copy, CLI installation guidance, runtime downloads, model links, and the README-linked [English](./troubleshooting.md) / [Chinese](./troubleshooting_zh.md) troubleshooting guides. Their original static, link, FAQ, and public HTTP checks passed, but the old LiteLLM `custom_openai` assertion was incorrect for transcription. The September 29 correction requires visible `openai/sensevoice` and an explicit self-hosted `api_base`, rejects the old route and checkpoint-ID example, and updates both ecosystem source pages. Source validation is separate from publication; it does not establish that the live website has deployed the correction. |
|
||||
| `modelscope/FunClip#188/#189`, `QwenAudio/Fun-ASR#151`, `QwenAudio/SenseVoice#327` four-repo community templates | Reduces maintainer follow-up and makes first external contributions easier to review across the four core repositories | Merged on 2026-07-23 after the four-repo template inventory. The four core repositories now have `CONTRIBUTING.md`, issue templates, and PR templates. FunClip gained bug/feature issue templates, a PR template, CONTRIBUTING.md, and template guards; Fun-ASR gained a PR template covering Nano, MLT, streaming, vLLM, GGUF, and HF/ModelScope routing; SenseVoice gained a PR template covering ASR, emotion/event tags, API/web UI/ONNX/Docker, GGUF, and model-card routing. Post-merge validation passed the new template guards plus each repo's lightweight docs/runtime smoke tests. |
|
||||
| `chatchat-space/Langchain-Chatchat#5479` FunASR/SenseVoice voice input | Puts FunASR in a 38k-star Chinese RAG/Agent app where local audio upload can feed existing Chat and knowledge-base flows | LauraGPT proposed an OpenAI-compatible ASR endpoint slice, including `asr.base_url`, multipart request shape, minimal `{ "text": "..." }` response, and mocked CI endpoint at https://github.com/chatchat-space/Langchain-Chatchat/issues/5479#issuecomment-4901293565. Monitor stale handling and maintainer appetite for a first audio-upload transcription path. |
|
||||
| `royshil/obs-localvocal#314` SenseVoice/Paraformer engine option | Opens an OBS live-captioning path where SenseVoice/Paraformer can be evaluated through a cleaner ASR-engine boundary instead of a Whisper-only runtime | Maintainer is interested but capacity-limited until mid/late August. LauraGPT mapped current Whisper coupling points and suggested a facade-only first refactor at https://github.com/royshil/obs-localvocal/issues/314#issuecomment-4909662013; wait for a refactor branch or review request before proposing Sherpa-ONNX/FunASR runtime code. |
|
||||
@@ -302,7 +302,7 @@ High-star feature requests and roadmap issues are earlier in the funnel than PRs
|
||||
| `zts212653/clowder-ai#1083` Qwen3-ASR service unification | Puts Qwen3-ASR into a user-facing local STT service slot by making it a `whisper-stt` backend variant instead of a separate, easier-to-misconfigure service | Merged on 2026-07-13 as `704bb735cae01be4bcd4089427bc6983c99a43a3` after fixes for Rosetta hardware detection, Qwen3-ASR install/server dispatch, async backend locking, temp WAV cleanup, and stale setup docs. The upstream service slot is already discoverable from the FunASR EN/ZH community integrations pages. |
|
||||
| `run-llama/llama_index#21958` FunASR endpoint reader | Puts FunASR behind a LlamaIndex reader for OpenAI-compatible transcription endpoints used in RAG and agent pipelines | LauraGPT rechecked head `07a8599deaebe5e7a559d62174e7a872870c2f7e` at https://github.com/run-llama/llama_index/pull/21958#issuecomment-4905345545; author acknowledged the shared-reader pytest caveat at https://github.com/run-llama/llama_index/pull/21958#issuecomment-4905384536. Keep the endpoint contract clear and avoid forcing local `funasr` dependencies into the main package. |
|
||||
| `run-llama/llama_index#21996` local FunASR reader | Gives LlamaIndex users a local SenseVoice/FunASR reader for private transcription workflows | Keep optional dependencies isolated, verify the reader does not affect default installs, and watch for maintainer guidance on package extras. |
|
||||
| `BerriAI/litellm-docs#610` FunASR transcription proxy docs | Puts FunASR/SenseVoice behind LiteLLM's existing OpenAI-compatible `custom_openai` audio transcription route, reaching teams that already deploy LiteLLM gateways | Rebased across the upstream `docs/audio_transcription.md` conflict on 2026-07-23; head `1a70fa02` is mergeable with CodeRabbit, Vercel, and Vercel preview comments all green. The docs now keep upstream's `mock_testing_fallbacks` deprecation note for Proxy requests, use `funasr>=1.3.26`, preserve `api_base: http://localhost:8000/v1`, and avoid installing `vllm` in the basic setup path. Local validation passed `git diff --check`, content assertions, and `npm run build`; build warnings are existing site-wide Docusaurus broken links outside `/docs/audio_transcription`. Evidence posted at https://github.com/BerriAI/litellm-docs/pull/610#issuecomment-5049681978. |
|
||||
| `BerriAI/litellm-docs#610` FunASR transcription proxy docs | Routes self-hosted FunASR/SenseVoice transcription through LiteLLM's `openai/sensevoice` provider/model with an explicit backend `api_base`, reaching teams that deploy LiteLLM gateways | At 2026-09-29 02:26 UTC, signed head `64ab922e497ce0f181565e31c9b29025f48b01e6` includes 883 upstream main commits and corrects the unsupported `custom_openai` transcription provider, checkpoint-ID model name, and weak Proxy master-key example. Native LiteLLM 1.103.0 / OpenAI SDK transport checks, FunASR 1.4.16 ASGI tests with inference stubbed, and the exact-main master-key guard passed 14 focused tests; the documentation build produced 1,357 HTML pages and desktop/mobile browser checks passed. No speech model was downloaded or inferred, and a live Proxy was not booted. The PR remains open: hosted docs checks require approval with zero jobs run, and Vercel deployment is pending. The green July checks and successful preview-comment integration are not evidence of a current successful deployment. [Current PR and validation](https://github.com/BerriAI/litellm-docs/pull/610). |
|
||||
| `mem0ai/mem0#5571` optional FunASR transcription helper | Adds local FunASR transcription to a high-star memory layer used by agent builders | Keep FunASR optional and make examples clear about local model downloads; the current failing Vercel preview is a Mem0 team authorization gate, not a FunASR code failure, so wait for maintainer-side preview access or review. |
|
||||
| `Significant-Gravitas/AutoGPT#13500` configurable transcription endpoints | Opens a path from AutoGPT's high-visibility agent platform to local FunASR/SenseVoice gateways through the existing OpenAI-compatible transcription route | Merged on 2026-07-22 after the review and status gates cleared. The merged path keeps transcription provider configuration generic, so FunASR/SenseVoice can fit through an OpenAI-compatible endpoint without adding a heavyweight toolkit dependency to AutoGPT. |
|
||||
| `Vexa-ai/vexa#810` custom STT endpoint documentation | Makes bring-your-own OpenAI-compatible transcription servers visible in a meeting-bot docs surface, giving SenseVoiceSmall and Fun-ASR-Nano users a clean path to point Vexa at private FunASR/SenseVoice gateways | Delivered via Vexa's community-batch release `#867`, merged to `main` as `e617d839` and shipped in `v0.12.16` with `docs/docs/how-to/custom-stt.mdx`. Maintainer confirmed on 2026-07-22 that #810 is superseded by the delivered release content, so treat the closed #810 PR as completed evidence rather than an active merge-card gate. |
|
||||
|
||||
@@ -16,9 +16,7 @@ import xml.etree.ElementTree as ET
|
||||
|
||||
|
||||
BASE_URL = "https://www.funasr.com"
|
||||
DONOR_PAGE_URLS = frozenset(
|
||||
(f"{BASE_URL}/donors.html", f"{BASE_URL}/en/donors.html")
|
||||
)
|
||||
DONOR_PAGE_URLS = frozenset((f"{BASE_URL}/donors.html", f"{BASE_URL}/en/donors.html"))
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -57,13 +55,18 @@ PAGE_CONTRACTS: dict[str, PageContract] = {
|
||||
),
|
||||
f"{BASE_URL}/ecosystem.html": PageContract(
|
||||
required=(
|
||||
"36K+",
|
||||
"37K+",
|
||||
"/donors.html",
|
||||
"LiteLLM",
|
||||
"custom_openai",
|
||||
"54.3K stars",
|
||||
),
|
||||
visible_required=("api_base",),
|
||||
visible_patterns=(
|
||||
("openai/sensevoice", r"(?<![\w/])openai/sensevoice(?![\w./+-])"),
|
||||
(
|
||||
"http://127.0.0.1:8000/v1",
|
||||
r"(?<![\w/])http://127\.0\.0\.1:8000/v1(?![\w/?#.:+-])",
|
||||
),
|
||||
("FunASR 官方插件 0.1.1", r"FunASR 官方插件 0\.1\.1(?![\w.+-])"),
|
||||
("最大 25 MB 音频上传", r"最大 (?<!\d)25 MB 音频上传"),
|
||||
),
|
||||
@@ -75,17 +78,22 @@ PAGE_CONTRACTS: dict[str, PageContract] = {
|
||||
"https://github.com/0xShug0/audio.cpp/blob/1778b23a5f6a4951c788e4bb0e7baa04f20012a2/docs/models/fun_asr_nano.md",
|
||||
"https://github.com/RVC-Boss/GPT-SoVITS/pull/2824",
|
||||
),
|
||||
forbidden=("16K+",),
|
||||
forbidden=("16K+", "custom_openai", "openai/FunAudioLLM/SenseVoiceSmall"),
|
||||
),
|
||||
f"{BASE_URL}/en/ecosystem.html": PageContract(
|
||||
required=(
|
||||
"36K+",
|
||||
"37K+",
|
||||
"/en/donors.html",
|
||||
"LiteLLM",
|
||||
"custom_openai",
|
||||
"54.3K stars",
|
||||
),
|
||||
visible_required=("api_base",),
|
||||
visible_patterns=(
|
||||
("openai/sensevoice", r"(?<![\w/])openai/sensevoice(?![\w./+-])"),
|
||||
(
|
||||
"http://127.0.0.1:8000/v1",
|
||||
r"(?<![\w/])http://127\.0\.0\.1:8000/v1(?![\w/?#.:+-])",
|
||||
),
|
||||
("FunASR plugin 0.1.1", r"FunASR plugin 0\.1\.1(?![\w.+-])"),
|
||||
("25 MB uploads", r"(?<!\d)25 MB uploads"),
|
||||
),
|
||||
@@ -97,7 +105,7 @@ PAGE_CONTRACTS: dict[str, PageContract] = {
|
||||
"https://github.com/0xShug0/audio.cpp/blob/1778b23a5f6a4951c788e4bb0e7baa04f20012a2/docs/models/fun_asr_nano.md",
|
||||
"https://github.com/RVC-Boss/GPT-SoVITS/pull/2824",
|
||||
),
|
||||
forbidden=("16K+",),
|
||||
forbidden=("16K+", "custom_openai", "openai/FunAudioLLM/SenseVoiceSmall"),
|
||||
),
|
||||
f"{BASE_URL}/donors.html": PageContract(
|
||||
required=(
|
||||
@@ -444,9 +452,7 @@ class _VisibleContentCollector(HTMLParser):
|
||||
tag = tag.lower()
|
||||
attributes = {name.lower(): value for name, value in attrs}
|
||||
hidden = (
|
||||
self._parent_hidden
|
||||
or tag in _HIDDEN_TAGS
|
||||
or self._has_hidden_attribute(attributes)
|
||||
self._parent_hidden or tag in _HIDDEN_TAGS or self._has_hidden_attribute(attributes)
|
||||
)
|
||||
if not hidden:
|
||||
if tag == "img" and attributes.get("src"):
|
||||
@@ -460,9 +466,7 @@ class _VisibleContentCollector(HTMLParser):
|
||||
tag = tag.lower()
|
||||
attributes = {name.lower(): value for name, value in attrs}
|
||||
hidden = (
|
||||
self._parent_hidden
|
||||
or tag in _HIDDEN_TAGS
|
||||
or self._has_hidden_attribute(attributes)
|
||||
self._parent_hidden or tag in _HIDDEN_TAGS or self._has_hidden_attribute(attributes)
|
||||
)
|
||||
if not hidden and tag == "img" and attributes.get("src"):
|
||||
self.images.add(attributes["src"])
|
||||
@@ -541,10 +545,7 @@ class _DirectoryNavigationCollector(HTMLParser):
|
||||
tag = tag.lower()
|
||||
attributes = {name.lower(): value for name, value in attrs}
|
||||
if not self._stack:
|
||||
is_navigation = (
|
||||
tag == "div"
|
||||
and "nav-links" in (attributes.get("class") or "").split()
|
||||
)
|
||||
is_navigation = tag == "div" and "nav-links" in (attributes.get("class") or "").split()
|
||||
if not is_navigation:
|
||||
return
|
||||
self.found_navigation = True
|
||||
@@ -619,9 +620,7 @@ def validate_navigation(pages: dict[str, str]) -> list[str]:
|
||||
directory_links: list[DirectoryLink] = []
|
||||
for link in navigation_links:
|
||||
target = urlparse(urljoin(url, link.href))
|
||||
is_language_toggle = (
|
||||
target.netloc == "www.funasr.com" and target.path == alternate_path
|
||||
)
|
||||
is_language_toggle = target.netloc == "www.funasr.com" and target.path == alternate_path
|
||||
is_github_action = target.netloc == "github.com"
|
||||
if not is_language_toggle and not is_github_action:
|
||||
directory_links.append(link)
|
||||
@@ -633,21 +632,14 @@ def validate_navigation(pages: dict[str, str]) -> list[str]:
|
||||
expected_links = [link for link in navigation_links if link.href == expected]
|
||||
if not any(link.label in expected_labels for link in expected_links):
|
||||
label_description = "` or `".join(expected_labels)
|
||||
failures.append(
|
||||
f"{url}: `{expected}` must use visible label `{label_description}`"
|
||||
)
|
||||
if expected in links and (
|
||||
not directory_links or directory_links[-1].href != expected
|
||||
):
|
||||
failures.append(f"{url}: `{expected}` must use visible label `{label_description}`")
|
||||
if expected in links and (not directory_links or directory_links[-1].href != expected):
|
||||
failures.append(f"{url}: `{expected}` must be the last directory link")
|
||||
if links.count(expected) > 1:
|
||||
failures.append(
|
||||
f"{url}: directory navigation contains duplicate `{expected}`"
|
||||
)
|
||||
failures.append(f"{url}: directory navigation contains duplicate `{expected}`")
|
||||
if wrong_language in links:
|
||||
failures.append(
|
||||
f"{url}: directory navigation contains wrong-language "
|
||||
f"`{wrong_language}`"
|
||||
f"{url}: directory navigation contains wrong-language " f"`{wrong_language}`"
|
||||
)
|
||||
return failures
|
||||
|
||||
@@ -723,9 +715,7 @@ def validate_assets(assets: dict[str, bytes]) -> list[str]:
|
||||
return failures
|
||||
|
||||
|
||||
def _fetch_bytes(
|
||||
url: str, timeout: float, retries: int, require_exact_url: bool = False
|
||||
) -> bytes:
|
||||
def _fetch_bytes(url: str, timeout: float, retries: int, require_exact_url: bool = False) -> bytes:
|
||||
last_error: Exception | None = None
|
||||
for attempt in range(retries + 1):
|
||||
try:
|
||||
@@ -741,17 +731,13 @@ def _fetch_bytes(
|
||||
raise last_error
|
||||
|
||||
|
||||
def _fetch_url(
|
||||
url: str, timeout: float, retries: int, require_exact_url: bool = False
|
||||
) -> str:
|
||||
def _fetch_url(url: str, timeout: float, retries: int, require_exact_url: bool = False) -> str:
|
||||
return _fetch_bytes(
|
||||
url,
|
||||
timeout=timeout,
|
||||
retries=retries,
|
||||
require_exact_url=require_exact_url,
|
||||
).decode(
|
||||
"utf-8", errors="replace"
|
||||
)
|
||||
).decode("utf-8", errors="replace")
|
||||
|
||||
|
||||
def fetch_pages(timeout: float, retries: int = 3) -> dict[str, str]:
|
||||
@@ -790,9 +776,7 @@ def main(argv: list[str] | None = None) -> int:
|
||||
parser.add_argument("--retries", type=int, default=3)
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
navigation_pages = fetch_navigation_pages(
|
||||
timeout=args.timeout, retries=args.retries
|
||||
)
|
||||
navigation_pages = fetch_navigation_pages(timeout=args.timeout, retries=args.retries)
|
||||
pages = {}
|
||||
for url in PAGE_CONTRACTS:
|
||||
if url in DONOR_PAGE_URLS:
|
||||
@@ -810,9 +794,7 @@ def main(argv: list[str] | None = None) -> int:
|
||||
all_navigation_pages.update(pages)
|
||||
failures = validate_pages(pages)
|
||||
failures.extend(validate_navigation(all_navigation_pages))
|
||||
failures.extend(
|
||||
validate_assets(fetch_assets(timeout=args.timeout, retries=args.retries))
|
||||
)
|
||||
failures.extend(validate_assets(fetch_assets(timeout=args.timeout, retries=args.retries)))
|
||||
if failures:
|
||||
print("funasr.com static page contract failed:", file=sys.stderr)
|
||||
for failure in failures:
|
||||
|
||||
@@ -50,7 +50,7 @@ def test_website_contract_accepts_current_public_copy():
|
||||
vLLM Acceleration
|
||||
""",
|
||||
"https://www.funasr.com/ecosystem.html": """
|
||||
<div class="stat-num">36K+</div>
|
||||
<div class="stat-num">37K+</div>
|
||||
<a href="/donors.html">功德榜</a>
|
||||
<a href="https://github.com/BerriAI/litellm">LiteLLM</a>
|
||||
<a href="https://github.com/modelscope/FunClip/releases/tag/v2.1.1">FunClip v2.1.1</a>
|
||||
@@ -58,7 +58,7 @@ def test_website_contract_accepts_current_public_copy():
|
||||
<a href="https://github.com/0xShug0/audio.cpp/pull/155">merged PR</a>
|
||||
<a href="https://github.com/0xShug0/audio.cpp/blob/1778b23a5f6a4951c788e4bb0e7baa04f20012a2/docs/models/fun_asr_nano.md">pinned guide</a>
|
||||
<a href="https://github.com/RVC-Boss/GPT-SoVITS/pull/2824">merged Transformers fix</a>
|
||||
<div>custom_openai</div>
|
||||
<div>openai/sensevoice api_base: http://127.0.0.1:8000/v1</div>
|
||||
<div>54.3K stars</div>
|
||||
<a href="https://marketplace.dify.ai/plugin/langgenius/funasr">
|
||||
FunASR 官方插件 0.1.1
|
||||
@@ -66,7 +66,7 @@ def test_website_contract_accepts_current_public_copy():
|
||||
<div>支持最大 25 MB 音频上传</div>
|
||||
""",
|
||||
"https://www.funasr.com/en/ecosystem.html": """
|
||||
<div class="stat-num">36K+</div>
|
||||
<div class="stat-num">37K+</div>
|
||||
<a href="/en/donors.html">Thanks</a>
|
||||
<a href="https://github.com/BerriAI/litellm">LiteLLM</a>
|
||||
<a href="https://github.com/modelscope/FunClip/releases/tag/v2.1.1">FunClip v2.1.1</a>
|
||||
@@ -74,7 +74,7 @@ def test_website_contract_accepts_current_public_copy():
|
||||
<a href="https://github.com/0xShug0/audio.cpp/pull/155">merged PR</a>
|
||||
<a href="https://github.com/0xShug0/audio.cpp/blob/1778b23a5f6a4951c788e4bb0e7baa04f20012a2/docs/models/fun_asr_nano.md">pinned guide</a>
|
||||
<a href="https://github.com/RVC-Boss/GPT-SoVITS/pull/2824">merged Transformers fix</a>
|
||||
<div>custom_openai</div>
|
||||
<div>openai/sensevoice api_base: http://127.0.0.1:8000/v1</div>
|
||||
<div>54.3K stars</div>
|
||||
<a href="https://marketplace.dify.ai/plugin/langgenius/funasr">
|
||||
FunASR plugin 0.1.1
|
||||
@@ -277,9 +277,22 @@ def test_homepage_contract_accepts_current_product_site_build(tmp_path, monkeypa
|
||||
assert checker.validate_pages(pages) == []
|
||||
|
||||
|
||||
def test_llamacpp_comparison_contract_accepts_current_product_site_build(
|
||||
tmp_path, monkeypatch
|
||||
):
|
||||
def test_ecosystem_contract_accepts_current_product_site_build(tmp_path, monkeypatch):
|
||||
checker = _load_module()
|
||||
builder = _load_product_site_builder()
|
||||
builder.build(tmp_path)
|
||||
routes = ("ecosystem.html", "en/ecosystem.html")
|
||||
pages = {
|
||||
f"https://www.funasr.com/{route}": (tmp_path / route).read_text(encoding="utf-8")
|
||||
for route in routes
|
||||
}
|
||||
monkeypatch.setattr(
|
||||
checker, "PAGE_CONTRACTS", {url: checker.PAGE_CONTRACTS[url] for url in pages}
|
||||
)
|
||||
assert checker.validate_pages(pages) == []
|
||||
|
||||
|
||||
def test_llamacpp_comparison_contract_accepts_current_product_site_build(tmp_path, monkeypatch):
|
||||
checker = _load_module()
|
||||
builder = _load_product_site_builder()
|
||||
builder.build(tmp_path)
|
||||
@@ -288,9 +301,7 @@ def test_llamacpp_comparison_contract_accepts_current_product_site_build(
|
||||
"en/blog/funasr-llama-cpp-whisper-cpp-alternative.html",
|
||||
)
|
||||
pages = {
|
||||
f"https://www.funasr.com/{route}": (tmp_path / route).read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
f"https://www.funasr.com/{route}": (tmp_path / route).read_text(encoding="utf-8")
|
||||
for route in routes
|
||||
}
|
||||
monkeypatch.setattr(
|
||||
@@ -326,7 +337,7 @@ def test_ecosystem_contract_requires_current_release_and_native_runtime():
|
||||
"https://www.funasr.com/en/ecosystem.html",
|
||||
):
|
||||
contract = checker.PAGE_CONTRACTS[url]
|
||||
assert "36K+" in contract.required
|
||||
assert "37K+" in contract.required
|
||||
assert evidence_links <= set(contract.required_links)
|
||||
|
||||
|
||||
@@ -336,10 +347,10 @@ def test_ecosystem_contract_requires_live_dify_marketplace():
|
||||
pages = {
|
||||
url: """
|
||||
<body>
|
||||
<div class="stat-num">36K+</div>
|
||||
<div class="stat-num">37K+</div>
|
||||
<a href="/en/donors.html">Thanks</a>
|
||||
<a href="https://github.com/BerriAI/litellm">LiteLLM</a>
|
||||
<div>custom_openai</div>
|
||||
<div>openai/sensevoice api_base: http://127.0.0.1:8000/v1</div>
|
||||
<div>54.3K stars</div>
|
||||
<a href="https://github.com/langgenius/dify">Dify</a>
|
||||
</body>
|
||||
@@ -350,8 +361,7 @@ def test_ecosystem_contract_requires_live_dify_marketplace():
|
||||
|
||||
assert any(
|
||||
url in failure
|
||||
and "missing link `https://marketplace.dify.ai/plugin/langgenius/funasr`"
|
||||
in failure
|
||||
and "missing link `https://marketplace.dify.ai/plugin/langgenius/funasr`" in failure
|
||||
for failure in failures
|
||||
)
|
||||
assert any(
|
||||
@@ -359,15 +369,14 @@ def test_ecosystem_contract_requires_live_dify_marketplace():
|
||||
for failure in failures
|
||||
)
|
||||
assert any(
|
||||
url in failure and "visible text missing `25 MB uploads`" in failure
|
||||
for failure in failures
|
||||
url in failure and "visible text missing `25 MB uploads`" in failure for failure in failures
|
||||
)
|
||||
|
||||
|
||||
def _valid_english_ecosystem_html():
|
||||
return """
|
||||
<body>
|
||||
<div class="stat-num">36K+</div>
|
||||
<div class="stat-num">37K+</div>
|
||||
<a href="/en/donors.html">Thanks</a>
|
||||
<a href="https://github.com/BerriAI/litellm">LiteLLM</a>
|
||||
<a href="https://github.com/modelscope/FunClip/releases/tag/v2.1.1">FunClip v2.1.1</a>
|
||||
@@ -375,7 +384,7 @@ def _valid_english_ecosystem_html():
|
||||
<a href="https://github.com/0xShug0/audio.cpp/pull/155">merged PR</a>
|
||||
<a href="https://github.com/0xShug0/audio.cpp/blob/1778b23a5f6a4951c788e4bb0e7baa04f20012a2/docs/models/fun_asr_nano.md">pinned guide</a>
|
||||
<a href="https://github.com/RVC-Boss/GPT-SoVITS/pull/2824">merged Transformers fix</a>
|
||||
<div>custom_openai</div>
|
||||
<div>openai/sensevoice api_base: http://127.0.0.1:8000/v1</div>
|
||||
<div>54.3K stars</div>
|
||||
<a href="https://marketplace.dify.ai/plugin/langgenius/funasr">
|
||||
FunASR plugin 0.1.1
|
||||
@@ -385,6 +394,75 @@ def _valid_english_ecosystem_html():
|
||||
"""
|
||||
|
||||
|
||||
@pytest.fixture(params=["en/", ""])
|
||||
def litellm_ecosystem_page(request):
|
||||
language = request.param
|
||||
html = _valid_english_ecosystem_html()
|
||||
if not language:
|
||||
html = (
|
||||
html.replace("/en/donors.html", "/donors.html")
|
||||
.replace("FunASR plugin 0.1.1", "FunASR 官方插件 0.1.1")
|
||||
.replace("supports 25 MB uploads", "支持最大 25 MB 音频上传")
|
||||
)
|
||||
return f"https://www.funasr.com/{language}ecosystem.html", html
|
||||
|
||||
|
||||
def test_ecosystem_accepts_litellm_transcription_route(litellm_ecosystem_page):
|
||||
checker = _load_module()
|
||||
url, html = litellm_ecosystem_page
|
||||
assert not [
|
||||
failure for failure in checker.validate_pages({url: html}) if failure.startswith(url + ":")
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("legacy", ["custom_openai", "openai/FunAudioLLM/SenseVoiceSmall"])
|
||||
def test_ecosystem_rejects_legacy_litellm_routing(litellm_ecosystem_page, legacy):
|
||||
checker = _load_module()
|
||||
url, html = litellm_ecosystem_page
|
||||
failures = checker.validate_pages({url: html + f"<code>{legacy}</code>"})
|
||||
assert f"{url}: forbidden `{legacy}`" in failures
|
||||
|
||||
|
||||
@pytest.mark.parametrize("suffix", ["Small", "/model", "-extra"])
|
||||
def test_ecosystem_rejects_wrong_litellm_model_alias(litellm_ecosystem_page, suffix):
|
||||
checker = _load_module()
|
||||
url, html = litellm_ecosystem_page
|
||||
html = html.replace("openai/sensevoice", "openai/sensevoice" + suffix)
|
||||
failures = checker.validate_pages({url: html})
|
||||
assert f"{url}: visible text missing `openai/sensevoice`" in failures
|
||||
|
||||
|
||||
@pytest.mark.parametrize("required", ["openai/sensevoice", "api_base", "http://127.0.0.1:8000/v1"])
|
||||
def test_ecosystem_requires_visible_litellm_configuration(litellm_ecosystem_page, required):
|
||||
checker = _load_module()
|
||||
url, html = litellm_ecosystem_page
|
||||
html = html.replace(required, f"<span hidden>{required}</span>")
|
||||
failures = checker.validate_pages({url: html})
|
||||
assert f"{url}: visible text missing `{required}`" in failures
|
||||
|
||||
|
||||
@pytest.mark.parametrize("suffix", ["0", "/audio/transcriptions", "?route=other"])
|
||||
def test_ecosystem_rejects_wrong_litellm_api_base(litellm_ecosystem_page, suffix):
|
||||
checker = _load_module()
|
||||
url, html = litellm_ecosystem_page
|
||||
api_base = "http://127.0.0.1:8000/v1"
|
||||
html = html.replace(api_base, api_base + suffix)
|
||||
failures = checker.validate_pages({url: html})
|
||||
assert f"{url}: visible text missing `{api_base}`" in failures
|
||||
|
||||
|
||||
def test_ecosystem_source_uses_working_litellm_configuration(litellm_ecosystem_page):
|
||||
checker = _load_module()
|
||||
url, _ = litellm_ecosystem_page
|
||||
relative = url.removeprefix("https://www.funasr.com/")
|
||||
source = ROOT / "web-pages" / "product-site" / "legacy" / relative
|
||||
visible = checker.extract_visible_text(source.read_text())
|
||||
assert "openai/sensevoice" in visible
|
||||
assert "api_base" in visible
|
||||
assert "http://127.0.0.1:8000/v1" in visible
|
||||
assert "custom_openai" not in visible
|
||||
|
||||
|
||||
def test_ecosystem_contract_rejects_longer_dify_version():
|
||||
checker = _load_module()
|
||||
url = "https://www.funasr.com/en/ecosystem.html"
|
||||
@@ -393,9 +471,7 @@ def test_ecosystem_contract_rejects_longer_dify_version():
|
||||
|
||||
failures = checker.validate_pages(pages)
|
||||
|
||||
assert any(
|
||||
url in failure and "FunASR plugin 0.1.1" in failure for failure in failures
|
||||
)
|
||||
assert any(url in failure and "FunASR plugin 0.1.1" in failure for failure in failures)
|
||||
|
||||
|
||||
def test_ecosystem_contract_rejects_dify_version_suffixes():
|
||||
@@ -408,9 +484,7 @@ def test_ecosystem_contract_rejects_dify_version_suffixes():
|
||||
|
||||
failures = checker.validate_pages(pages)
|
||||
|
||||
assert any(
|
||||
url in failure and "FunASR plugin 0.1.1" in failure for failure in failures
|
||||
)
|
||||
assert any(url in failure and "FunASR plugin 0.1.1" in failure for failure in failures)
|
||||
|
||||
|
||||
def test_ecosystem_contract_rejects_larger_dify_upload_limit():
|
||||
@@ -475,8 +549,7 @@ def test_website_contract_includes_funclip_v210_launch():
|
||||
in checker.PAGE_CONTRACTS
|
||||
)
|
||||
assert (
|
||||
"https://www.funasr.com/img/funclip-v2-1-0-interface.jpg"
|
||||
in checker.STATIC_ASSET_CONTRACTS
|
||||
"https://www.funasr.com/img/funclip-v2-1-0-interface.jpg" in checker.STATIC_ASSET_CONTRACTS
|
||||
)
|
||||
|
||||
|
||||
@@ -523,17 +596,14 @@ def test_funclip_v210_contract_rejects_hidden_evidence_and_wrong_routes():
|
||||
for failure in failures
|
||||
)
|
||||
assert any(
|
||||
url in failure
|
||||
and "missing link `https://github.com/modelscope/FunClip`" in failure
|
||||
url in failure and "missing link `https://github.com/modelscope/FunClip`" in failure
|
||||
for failure in failures
|
||||
)
|
||||
assert any(
|
||||
url in failure and "missing link `/en/donors.html`" in failure
|
||||
for failure in failures
|
||||
url in failure and "missing link `/en/donors.html`" in failure for failure in failures
|
||||
)
|
||||
assert any(
|
||||
url in failure
|
||||
and "missing image `/img/funclip-v2-1-0-interface.jpg`" in failure
|
||||
url in failure and "missing image `/img/funclip-v2-1-0-interface.jpg`" in failure
|
||||
for failure in failures
|
||||
)
|
||||
|
||||
@@ -573,13 +643,11 @@ def test_funclip_v210_contract_rejects_zero_area_links():
|
||||
failures = checker.validate_pages(pages)
|
||||
|
||||
assert any(
|
||||
url in failure
|
||||
and "missing link `https://github.com/modelscope/FunClip`" in failure
|
||||
url in failure and "missing link `https://github.com/modelscope/FunClip`" in failure
|
||||
for failure in failures
|
||||
)
|
||||
assert any(
|
||||
url in failure and "missing link `/en/donors.html`" in failure
|
||||
for failure in failures
|
||||
url in failure and "missing link `/en/donors.html`" in failure for failure in failures
|
||||
)
|
||||
|
||||
|
||||
@@ -606,8 +674,7 @@ def test_v1328_contract_rejects_language_swapped_body():
|
||||
failures = checker.validate_pages(pages)
|
||||
|
||||
assert any(
|
||||
url in failure
|
||||
and "visible text missing `精确合并源码通过 118 项聚焦回归测试`" in failure
|
||||
url in failure and "visible text missing `精确合并源码通过 118 项聚焦回归测试`" in failure
|
||||
for failure in failures
|
||||
)
|
||||
|
||||
@@ -640,8 +707,7 @@ def test_v1328_contract_rejects_css_test_count_and_indirect_repo_link():
|
||||
for failure in failures
|
||||
)
|
||||
assert any(
|
||||
url in failure
|
||||
and "missing link `https://github.com/modelscope/FunASR`" in failure
|
||||
url in failure and "missing link `https://github.com/modelscope/FunASR`" in failure
|
||||
for failure in failures
|
||||
)
|
||||
|
||||
@@ -670,14 +736,12 @@ def test_v1328_contract_requires_visible_stop_and_sensevoice_copy():
|
||||
failures = checker.validate_pages(pages)
|
||||
|
||||
assert any(
|
||||
url in failure
|
||||
and "visible text missing `including STOP final decode`" in failure
|
||||
url in failure and "visible text missing `including STOP final decode`" in failure
|
||||
for failure in failures
|
||||
)
|
||||
assert any(
|
||||
url in failure
|
||||
and "visible text missing `SenseVoice users receive the subtitle alignment fix`"
|
||||
in failure
|
||||
and "visible text missing `SenseVoice users receive the subtitle alignment fix`" in failure
|
||||
for failure in failures
|
||||
)
|
||||
|
||||
@@ -725,16 +789,15 @@ def test_website_contract_reports_stale_runtime_and_star_copy():
|
||||
|
||||
pages = {url: "" for url in checker.PAGE_CONTRACTS}
|
||||
pages["https://www.funasr.com/ecosystem.html"] = "16K+"
|
||||
pages[
|
||||
"https://www.funasr.com/blog/funasr-llama-cpp-whisper-cpp-alternative.html"
|
||||
] = "runtime-llamacpp-v0.1.1"
|
||||
pages["https://www.funasr.com/blog/funasr-llama-cpp-whisper-cpp-alternative.html"] = (
|
||||
"runtime-llamacpp-v0.1.1"
|
||||
)
|
||||
pages["https://www.funasr.com/llama-cpp.html"] = "runtime-llamacpp-v0.1.1"
|
||||
|
||||
failures = checker.validate_pages(pages)
|
||||
|
||||
assert any(
|
||||
"ecosystem.html" in failure and "forbidden `16K+`" in failure
|
||||
for failure in failures
|
||||
"ecosystem.html" in failure and "forbidden `16K+`" in failure for failure in failures
|
||||
)
|
||||
assert any(
|
||||
"funasr-llama-cpp-whisper-cpp-alternative.html" in failure
|
||||
@@ -771,8 +834,7 @@ def test_website_contract_requires_visible_donor_usage_copy():
|
||||
failures = checker.validate_pages(pages)
|
||||
|
||||
assert any(
|
||||
"donors.html" in failure
|
||||
and "visible text missing `购买和维护服务器`" in failure
|
||||
"donors.html" in failure and "visible text missing `购买和维护服务器`" in failure
|
||||
for failure in failures
|
||||
)
|
||||
assert any(
|
||||
@@ -855,8 +917,7 @@ def test_navigation_contract_requires_donors_as_last_directory_link():
|
||||
|
||||
assert not any("ok.html" in failure for failure in failures)
|
||||
assert any(
|
||||
"missing.html" in failure and "missing `/en/donors.html`" in failure
|
||||
for failure in failures
|
||||
"missing.html" in failure and "missing `/en/donors.html`" in failure for failure in failures
|
||||
)
|
||||
assert any(
|
||||
"not-last.html" in failure and "must be the last directory link" in failure
|
||||
@@ -867,8 +928,7 @@ def test_navigation_contract_requires_donors_as_last_directory_link():
|
||||
for failure in failures
|
||||
)
|
||||
assert any(
|
||||
"wrong-language.html" in failure
|
||||
and "contains wrong-language `/donors.html`" in failure
|
||||
"wrong-language.html" in failure and "contains wrong-language `/donors.html`" in failure
|
||||
for failure in failures
|
||||
)
|
||||
assert not any("plain.html" in failure for failure in failures)
|
||||
@@ -903,23 +963,19 @@ def test_navigation_contract_requires_visible_correctly_labeled_donor_link():
|
||||
failures = checker.validate_navigation(pages)
|
||||
|
||||
assert any(
|
||||
"hidden.html" in failure
|
||||
and "missing `/donors.html`; link must be visible" in failure
|
||||
"hidden.html" in failure and "missing `/donors.html`; link must be visible" in failure
|
||||
for failure in failures
|
||||
)
|
||||
assert any(
|
||||
"wrong-label.html" in failure
|
||||
and "must use visible label `功德榜`" in failure
|
||||
"wrong-label.html" in failure and "must use visible label `功德榜`" in failure
|
||||
for failure in failures
|
||||
)
|
||||
assert any(
|
||||
"hidden-label.html" in failure
|
||||
and "missing `/donors.html`; link must be visible" in failure
|
||||
"hidden-label.html" in failure and "missing `/donors.html`; link must be visible" in failure
|
||||
for failure in failures
|
||||
)
|
||||
assert any(
|
||||
"empty.html" in failure
|
||||
and "missing `/en/donors.html`; link must be visible" in failure
|
||||
"empty.html" in failure and "missing `/en/donors.html`; link must be visible" in failure
|
||||
for failure in failures
|
||||
)
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
"css/index.1682178c.css": "b5f6034ce886c25773928c114e4290d1c491a3d081677b488810198efd76b4e1",
|
||||
"decoder.js": "f2023a28036b1ff58e5bdabbb72ea427f8faaf60e89acd78ff183c313bdb0a2c",
|
||||
"donors.html": "eec6b789b29c27d372925ae0ad3e13d4c6e62978b3c38c4da9e3aec129f10934",
|
||||
"ecosystem.html": "de151aebb8d807b21e719d59a54766f4998945310c97bbabf2ac31dae5c0a283",
|
||||
"ecosystem.html": "a3cdaf0fefdc10fd51e8f09c3239e338528f4700e0037c2eb9556d650f6faef9",
|
||||
"en/blog/cantonese-speech-recognition.html": "b9bd146198206f781c37f341c370a690dc83cd7163334e34b4e7f65e69af8ff2",
|
||||
"en/blog/chinese-speech-recognition.html": "16b68bd2a1122236c580c62275002ae2c0875ca14e32686e88d3b5ccfa9f56ef",
|
||||
"en/blog/fun-asr-nano-guide.html": "1746c708bd0606188de4266d8255984dd9e14634af33c54ad65a5f05b767be1b",
|
||||
@@ -91,7 +91,7 @@
|
||||
"en/blog/voice-activity-detection-python.html": "d12f5d0f4ae07c3434f6188d12f5603b8abb9578413b79133668638d1c5e18a7",
|
||||
"en/blog/which-funasr-model.html": "1207180aa929a3aa51db136c1098f4edc8f07405e2b56fe033998bf996f8ec7f",
|
||||
"en/donors.html": "eacb7f9434b1b29e13e9b0175bb9680556ee72ab4dbd155e68f9c912eb0212b1",
|
||||
"en/ecosystem.html": "81cec9464db159189ef29df884b124c795fce01ef08bfaae03d982696de13701",
|
||||
"en/ecosystem.html": "38356f6bdb5cd89988304be61f13707f4df9fa84c2a191701aaee475d7587902",
|
||||
"en/index.html": "d86effc3bf4d218eabd465cbbe026f62c98dd50110ddd58251d9d271a64882a2",
|
||||
"en/llama-cpp.html": "dbfbd125ebbf07f7d923c91b122818f8b09484ce54c1e80eba66a89d22dfb5b4",
|
||||
"en/models.html": "9806d01ba1807c2a6a88f7232102e185426fb8458bf51e1d7a8ce872352e1fd1",
|
||||
|
||||
@@ -276,7 +276,7 @@ footer a{color:#60a5fa}
|
||||
<div class="card">
|
||||
<div class="card-title"><a href="https://github.com/BerriAI/litellm">LiteLLM</a></div>
|
||||
<div class="card-stars">54.3K stars</div>
|
||||
<div class="card-desc">OpenAI 兼容 AI Gateway。FunASR / SenseVoice 可通过 <code>custom_openai</code> 作为自托管语音转写端点接入。</div>
|
||||
<div class="card-desc">OpenAI 兼容 AI Gateway。使用 <code>openai/sensevoice</code>,并显式设置 <code>api_base: http://127.0.0.1:8000/v1</code>,可接入同机自托管的 FunASR 转写服务。<code>sensevoice</code> 是服务模型别名,不是 checkpoint 仓库 ID;容器部署需使用容器内可访问的服务地址。<a href="https://github.com/BerriAI/litellm-docs/pull/610">配置文档 PR</a> 尚待上游合并。</div>
|
||||
<div><span class="card-tag">AI Gateway</span><span class="card-tag">OpenAI 兼容</span></div>
|
||||
</div>
|
||||
<div class="card">
|
||||
|
||||
@@ -277,7 +277,7 @@ footer a{color:#60a5fa}
|
||||
<div class="card">
|
||||
<div class="card-title"><a href="https://github.com/BerriAI/litellm">LiteLLM</a></div>
|
||||
<div class="card-stars">54.3K stars</div>
|
||||
<div class="card-desc">OpenAI-compatible AI Gateway. FunASR / SenseVoice can be routed through <code>custom_openai</code> as a self-hosted transcription endpoint.</div>
|
||||
<div class="card-desc">OpenAI-compatible AI Gateway. Use <code>openai/sensevoice</code> with an explicit <code>api_base: http://127.0.0.1:8000/v1</code> for a self-hosted FunASR transcription server on the same host. <code>sensevoice</code> is a server model alias, not a checkpoint repository ID; containers need a backend address reachable from inside the container. The <a href="https://github.com/BerriAI/litellm-docs/pull/610">configuration docs PR</a> is awaiting upstream merge.</div>
|
||||
<div><span class="card-tag">AI Gateway</span><span class="card-tag">OpenAI-compatible</span></div>
|
||||
</div>
|
||||
<div class="card">
|
||||
|
||||
Reference in New Issue
Block a user