|
4 | 4 | [](https://www.python.org/) |
5 | 5 | [](LICENSE) |
6 | 6 |
|
7 | | -Access the **full OpenRouter catalog (400+ models)** — chat, TTS, audio, image-generation, and |
8 | | -embedding models — directly inside Open WebUI, with provider routing, reasoning tokens, streaming, |
9 | | -fallbacks, and cache control out of the box. |
| 7 | +Access the **full OpenRouter catalog (400+ models)** — chat, TTS, audio (input + generation), |
| 8 | +image-generation, video-generation, and embedding models — directly inside Open WebUI, with |
| 9 | +provider routing, reasoning tokens, streaming, fallbacks, native media rendering, and cache |
| 10 | +control out of the box. |
10 | 11 |
|
11 | 12 | ## Table of Contents |
12 | 13 |
|
@@ -35,7 +36,11 @@ fallbacks, and cache control out of the box. |
35 | 36 |
|
36 | 37 | ## Features |
37 | 38 |
|
38 | | -- **Manifold pipe** — exposes the full OpenRouter catalog (chat, TTS, audio, image, embeddings) as native Open WebUI models in the model selector. Configurable via `OUTPUT_MODALITIES` and `MODEL_CATEGORY`. |
| 39 | +- **Manifold pipe** — exposes the full OpenRouter catalog (chat, TTS, audio, image, video, embeddings) as native Open WebUI models in the model selector. Configurable via `OUTPUT_MODALITIES` and `MODEL_CATEGORY`. |
| 40 | +- **Image generation** — `flux`, `gemini-image-preview`, and other image-output models work out of the box: returned `data:` URLs are uploaded to OWUI storage and embedded inline as `` so the chat client renders them natively. |
| 41 | +- **Video generation** — `google/veo-3.1*`, `kwaivgi/kling*`, `openai/sora*`, `bytedance/seedance*`, `minimax/hailuo*`, `alibaba/wan*`, `x-ai/grok-imagine-video` are routed to OpenRouter's asynchronous `/api/v1/videos` endpoint (auto-polling, configurable `VIDEO_POLL_INTERVAL` / `VIDEO_GENERATION_TIMEOUT`), then re-hosted as OWUI files and embedded inline. |
| 42 | +- **Audio generation** — `google/lyria-3-*-preview` (music) and `openai/gpt-audio*` (speech, auto pcm16 → WAV wrap for streaming) inject the required `modalities=["text","audio"]` + `audio={format,voice}` payload automatically, capture the base64 chunks, decode, upload, and embed as inline `<audio controls>`. |
| 43 | +- **SSRF-guarded media downloads** — polling URLs and signed download URLs are restricted to `openrouter.ai`; downloads are byte-capped (100 MiB video / 50 MiB audio) and MIME-whitelisted post-fetch. |
39 | 44 | - **Web search plugin** — attach OpenRouter's `web` plugin to any model with domain allow/deny lists, custom search prompt, and result-count limits. |
40 | 45 | - **Variant routing** — surface virtual `:nitro`/`:exacto`/`:thinking`/`:online`/`:free`/`:extended` model entries that route to OpenRouter's specialized profiles. |
41 | 46 | - **Service tier hint** — forward `flex` (cheaper/slower) or `priority` (faster) tiers to compatible providers. |
@@ -91,7 +96,7 @@ All OpenRouter models will appear in the model selector immediately. |
91 | 96 | git clone https://github.com/sena-labs/Open-WebUI-Pipe-OpenRouter.git |
92 | 97 | cd Open-WebUI-Pipe-OpenRouter |
93 | 98 | pip install -r requirements.txt |
94 | | -python test_pipe.py # 727 tests — verify everything is green |
| 99 | +python test_pipe.py # 868 tests — verify everything is green |
95 | 100 | ``` |
96 | 101 |
|
97 | 102 | ## Usage |
@@ -257,7 +262,7 @@ The pipe implements the **Manifold** pattern: one pipe entry point that surfaces |
257 | 262 | Open-WebUI-Pipe-OpenRouter/ |
258 | 263 | ├── openrouter_pipe.py # Main pipe source — install this in Open WebUI |
259 | 264 | ├── function.json # Open WebUI community manifest |
260 | | -├── test_pipe.py # Unit test suite (727 tests) |
| 265 | +├── test_pipe.py # Unit test suite (868 tests) |
261 | 266 | ├── integration_test.py # Live API integration tests (44 assertions) |
262 | 267 | ├── TESTING.md # Manual pre-release checklist |
263 | 268 | ├── SECURITY.md # Security policy |
@@ -287,7 +292,7 @@ It also removes `user` when sent as a dict (Open WebUI format) since OpenRouter |
287 | 292 | ## Development |
288 | 293 |
|
289 | 294 | ```bash |
290 | | -python test_pipe.py # Unit tests (727 tests) |
| 295 | +python test_pipe.py # Unit tests (868 tests) |
291 | 296 | python integration_test.py # Live API tests (requires OPENROUTER_API_KEY) |
292 | 297 | ``` |
293 | 298 |
|
@@ -383,14 +388,43 @@ A: `FALLBACK_MODELS` adds extra model IDs to the `models` array in the OpenRoute |
383 | 388 | primary model fails, OpenRouter automatically tries the next one. Non-streaming responses include |
384 | 389 | a "Responded by: model-id" attribution when a fallback handled the request. |
385 | 390 |
|
386 | | -**Q: I selected a TTS / embeddings / image-generation model and got an error — why?** |
387 | | - |
388 | | -A: The pipe routes every request through OpenRouter's `/chat/completions` endpoint. Models that |
389 | | -only expose a non-chat endpoint (e.g. pure TTS models served via `/audio/speech`) return an |
390 | | -"endpoint not supported" error from OpenRouter. The pipe surfaces that error verbatim. Chat |
391 | | -completion models that *output* audio or images (e.g. `openai/gpt-audio`) work normally — their |
392 | | -audio transcript and generated images are rendered inline. To hide non-chat models from the |
393 | | -selector entirely, set `OUTPUT_MODALITIES = text`. |
| 391 | +**Q: How do image / video / audio generation models work in the pipe?** |
| 392 | + |
| 393 | +A: The pipe inspects each model's `architecture.output_modalities` (from OpenRouter's `/models` |
| 394 | +catalog) and routes accordingly: |
| 395 | + |
| 396 | +- **Image-output models** (`flux`, `gemini-image-preview`, ...) — served via the standard |
| 397 | + `/chat/completions` endpoint. The returned base64 `data:` URLs in `choices[0].message.images` |
| 398 | + are uploaded through Open WebUI's file-upload helper and the message content is rewritten to |
| 399 | + `` so the OWUI chat renders them inline. |
| 400 | +- **Video-output models** (`veo`, `kling`, `sora`, `seedance`, `hailuo`, `wan`, `grok-imagine`) |
| 401 | + — NOT served by `/chat/completions` (that endpoint 500s for them). The pipe submits to |
| 402 | + `POST /api/v1/videos`, polls the returned `polling_url` every `VIDEO_POLL_INTERVAL` |
| 403 | + (default 5 s) up to `VIDEO_GENERATION_TIMEOUT` (default 600 s), downloads the MP4 once the |
| 404 | + job is `completed`, re-hosts it through OWUI, and embeds as a native `<video controls>` |
| 405 | + element. Status updates appear in the OWUI status shimmer during polling. |
| 406 | +- **Audio-output models** (`google/lyria-3-clip-preview`, `google/lyria-3-pro-preview`, |
| 407 | + `openai/gpt-audio`, `openai/gpt-audio-mini`) — served by `/chat/completions` but only |
| 408 | + emit audio when the request includes `modalities=["text","audio"]` + an `audio={format,voice}` |
| 409 | + object + `stream=true`. The pipe injects all three automatically. For OpenAI's `gpt-audio*` |
| 410 | + the format is forced to `pcm16` (the upstream's only supported format with `stream=true`) |
| 411 | + and the raw PCM is wrapped in a WAV container before upload so the browser can play it. |
| 412 | + For Lyria the default is mp3. |
| 413 | + |
| 414 | +Bytes are downloaded with hard caps (100 MiB video / 50 MiB audio), the post-fetch MIME is |
| 415 | +checked against a per-modality whitelist, and the `polling_url` + `unsigned_urls[0]` are |
| 416 | +restricted to `openrouter.ai` (or `*.openrouter.ai`) so a compromised upstream cannot redirect |
| 417 | +the Authorization bearer to an attacker-controlled host. |
| 418 | + |
| 419 | +To hide non-chat models from the selector entirely, set `OUTPUT_MODALITIES = text`. |
| 420 | + |
| 421 | +**Q: I selected a pure-embeddings or pure-TTS model and got an error — why?** |
| 422 | + |
| 423 | +A: The pipe routes those through `/chat/completions` (the same endpoint that backs every other |
| 424 | +chat). Models that only expose a non-chat endpoint (e.g. pure TTS models served via |
| 425 | +`/audio/speech`) return an "endpoint not supported" error from OpenRouter; the pipe surfaces |
| 426 | +that error verbatim. Use `OUTPUT_MODALITIES = text,image,audio,video` (or `all`) in the valves |
| 427 | +to control which modalities appear in the selector. |
394 | 428 |
|
395 | 429 | ## License |
396 | 430 |
|
|
0 commit comments