Documentation: No user-facing config; behaviour is automatic. Happy to add a docs PR if maintainers want one (the SVG directory has a README explaining the vendoring/license).
Dependencies: No new Python deps. Adds 14 vendored SVG files (~32 KB total) under backend/open_webui/static/providers/ from @lobehub/icons-static-svg (MIT, attribution preserved).
Testing: New pytest module with 42 cases (whitelist coverage, host/model precedence, unknown-id fallthrough, asset existence). Manually verified the fallback chain in the route locally.
Agentic AI Code: This PR was drafted with AI assistance and then manually reviewed, the regex set was hand-curated, the asset-vendoring tradeoff was made deliberately (see "judgment calls" below), and the test matrix was hand-written to cover real-world id shapes. Please flag anything that looks off and I'll address it.
Code review: Self-reviewed; ran ruff check and ruff format on the new files.
Design & Architecture: No new settings — the override path (admin-set meta.profile_image_url) keeps full control. The new lookup is a tail fallback, before the existing favicon fallback.
Git Hygiene: One atomic commit on feat/auto-provider-logos, rebased on dev.
Title Prefix:feat:
Changelog Entry
Description
Today, models supplied by an upstream connection (OpenAI, OpenRouter, Ollama, ...) show the grey OI placeholder unless an admin manually creates a model overlay row with a profile_image_url. With this change, adding gpt-5, anthropic/claude-opus-4, gemini-2.5-pro, deepseek-v3, etc. to a connection's allowed model list shows the right provider logo immediately. Zero config.
The override path is unchanged: an admin-supplied meta.profile_image_url always wins. The regex match runs only when no DB image is set, and before the existing favicon fallback — so unrecognised ids behave exactly like today.
Added
Auto-assigned provider logos in GET /api/v1/models/model/profile/image. When the database has no profile_image_url for a model, the route now matches the model id against a small regex table and redirects to a vendored brand SVG.
Vendored SVGs under backend/open_webui/static/providers/ (sourced from lobehub/lobe-icons, MIT — LICENSE and attribution README.md included). Covers OpenAI, Anthropic/Claude, Google/Gemini/Gemma, DeepSeek, Qwen, Zhipu/GLM, Mistral, xAI/Grok, Meta/Llama, Cohere, Perplexity, Moonshot/Kimi, Groq, Ollama (14 files, ~32 KB total).
backend/open_webui/utils/provider_logos.py — the matcher module. Documented matching philosophy and how to extend the rule set.
backend/open_webui/routers/models.py::get_model_profile_image — adds the new fallback step between the DB-image lookup and the favicon fallback. No behaviour change for models that already have a profile_image_url.
CDN vs bundled SVGs. I bundled. Lobe-icons is MIT-licensed, the SVGs are tiny (~32 KB total), and self-hosting avoids a runtime third-party dependency, works offline, and prevents leaking model identifiers in browser-side requests for default avatars. If you'd rather pull at request time from cdn.jsdelivr.net, happy to switch.
Provider precedence in host/model ids. For ids like groq/llama3-70b the matcher returns the Llama (model brand) logo, not Groq (inference host). Reasoning: it's still a Llama model, just hosted by Groq. The inference-host rules are placed below the model-brand rules so they only fire when no model brand is in the id (e.g. the bare id groq). Pinned by tests.
No new env var / setting. A toggle felt unnecessary — admins who want a specific image for a model already have the existing meta.profile_image_url override, which always wins. Happy to add one if you'd rather make the auto-fallback opt-in.
Provider coverage scope. Conservative on purpose — covered the brands users are most likely to enable today. Adding a provider is one regex rule + one SVG + one test row; the matcher's docstring spells out the steps.
Additional Information
Implementation references the existing _safe_static_redirect_path pattern in the same file.
The new utility lives at open_webui/utils/provider_logos.py (top-level utils module, like headers.py / misc.py); it does not import from any router or model.
Related: changelog entry "Default model profile images now redirect to a shared static path" (#24015) — same direction (cheap static redirects instead of per-request file reads).
Screenshots or Videos
I don't have a deployed instance with an array of brand-named models to capture a clean before/after on. The behaviour is verifiable from the test matrix:
By submitting this pull request, I confirm that I have read and fully agree to the Contributor License Agreement (CLA), and I am providing my contributions under its terms.
🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.
## 📋 Pull Request Information
**Original PR:** https://github.com/open-webui/open-webui/pull/24173
**Author:** [@ikamensh](https://github.com/ikamensh)
**Created:** 4/27/2026
**Status:** ❌ Closed
**Base:** `dev` ← **Head:** `feat/auto-provider-logos`
---
### 📝 Commits (1)
- [`ad850f8`](https://github.com/open-webui/open-webui/commit/ad850f872f67a607e26d20b8824427206a7757b8) feat: auto-assign provider logos to models by id
### 📊 Changes
**19 files changed** (+334 additions, -0 deletions)
<details>
<summary>View changed files</summary>
📝 `backend/open_webui/routers/models.py` (+13 -0)
➕ `backend/open_webui/static/providers/LICENSE` (+21 -0)
➕ `backend/open_webui/static/providers/README.md` (+30 -0)
➕ `backend/open_webui/static/providers/claude-color.svg` (+1 -0)
➕ `backend/open_webui/static/providers/cohere-color.svg` (+1 -0)
➕ `backend/open_webui/static/providers/deepseek-color.svg` (+1 -0)
➕ `backend/open_webui/static/providers/gemini-color.svg` (+1 -0)
➕ `backend/open_webui/static/providers/groq.svg` (+1 -0)
➕ `backend/open_webui/static/providers/meta-color.svg` (+1 -0)
➕ `backend/open_webui/static/providers/mistral-color.svg` (+1 -0)
➕ `backend/open_webui/static/providers/moonshot.svg` (+1 -0)
➕ `backend/open_webui/static/providers/ollama.svg` (+1 -0)
➕ `backend/open_webui/static/providers/openai.svg` (+1 -0)
➕ `backend/open_webui/static/providers/perplexity-color.svg` (+1 -0)
➕ `backend/open_webui/static/providers/qwen-color.svg` (+1 -0)
➕ `backend/open_webui/static/providers/xai.svg` (+1 -0)
➕ `backend/open_webui/static/providers/zhipu-color.svg` (+1 -0)
➕ `backend/open_webui/test/util/test_provider_logos.py` (+139 -0)
➕ `backend/open_webui/utils/provider_logos.py` (+117 -0)
</details>
### 📄 Description
# Pull Request Checklist
- [x] **Target branch:** Targets `dev`.
- [x] **Description:** See below.
- [x] **Changelog:** Entry below.
- [ ] **Documentation:** No user-facing config; behaviour is automatic. Happy to add a docs PR if maintainers want one (the SVG directory has a README explaining the vendoring/license).
- [x] **Dependencies:** No new Python deps. Adds 14 vendored SVG files (~32 KB total) under `backend/open_webui/static/providers/` from `@lobehub/icons-static-svg` (MIT, attribution preserved).
- [x] **Testing:** New pytest module with 42 cases (whitelist coverage, host/model precedence, unknown-id fallthrough, asset existence). Manually verified the fallback chain in the route locally.
- [ ] **Agentic AI Code:** This PR was drafted with AI assistance and then manually reviewed, the regex set was hand-curated, the asset-vendoring tradeoff was made deliberately (see "judgment calls" below), and the test matrix was hand-written to cover real-world id shapes. Please flag anything that looks off and I'll address it.
- [x] **Code review:** Self-reviewed; ran `ruff check` and `ruff format` on the new files.
- [x] **Design & Architecture:** No new settings — the override path (admin-set `meta.profile_image_url`) keeps full control. The new lookup is a tail fallback, before the existing favicon fallback.
- [x] **Git Hygiene:** One atomic commit on `feat/auto-provider-logos`, rebased on `dev`.
- [x] **Title Prefix:** `feat:`
# Changelog Entry
### Description
Today, models supplied by an upstream connection (OpenAI, OpenRouter, Ollama, ...) show the grey ``OI`` placeholder unless an admin manually creates a model overlay row with a `profile_image_url`. With this change, adding `gpt-5`, `anthropic/claude-opus-4`, `gemini-2.5-pro`, `deepseek-v3`, etc. to a connection's allowed model list shows the right provider logo immediately. Zero config.
The override path is unchanged: an admin-supplied `meta.profile_image_url` always wins. The regex match runs *only* when no DB image is set, and *before* the existing favicon fallback — so unrecognised ids behave exactly like today.
### Added
- **Auto-assigned provider logos** in `GET /api/v1/models/model/profile/image`. When the database has no `profile_image_url` for a model, the route now matches the model id against a small regex table and redirects to a vendored brand SVG.
- **Vendored SVGs** under `backend/open_webui/static/providers/` (sourced from [lobehub/lobe-icons](https://github.com/lobehub/lobe-icons), MIT — `LICENSE` and attribution `README.md` included). Covers OpenAI, Anthropic/Claude, Google/Gemini/Gemma, DeepSeek, Qwen, Zhipu/GLM, Mistral, xAI/Grok, Meta/Llama, Cohere, Perplexity, Moonshot/Kimi, Groq, Ollama (14 files, ~32 KB total).
- `backend/open_webui/utils/provider_logos.py` — the matcher module. Documented matching philosophy and how to extend the rule set.
- `backend/open_webui/test/util/test_provider_logos.py` — 42-case pytest suite.
### Changed
- `backend/open_webui/routers/models.py::get_model_profile_image` — adds the new fallback step between the DB-image lookup and the favicon fallback. No behaviour change for models that already have a `profile_image_url`.
### Removed / Deprecated / Fixed / Security / Breaking Changes
None.
---
### Judgment calls
A few things worth calling out for review:
1. **CDN vs bundled SVGs.** I bundled. Lobe-icons is MIT-licensed, the SVGs are tiny (~32 KB total), and self-hosting avoids a runtime third-party dependency, works offline, and prevents leaking model identifiers in browser-side requests for default avatars. If you'd rather pull at request time from `cdn.jsdelivr.net`, happy to switch.
2. **Provider precedence in `host/model` ids.** For ids like `groq/llama3-70b` the matcher returns the *Llama* (model brand) logo, not Groq (inference host). Reasoning: it's still a Llama model, just hosted by Groq. The inference-host rules are placed below the model-brand rules so they only fire when no model brand is in the id (e.g. the bare id `groq`). Pinned by tests.
3. **No new env var / setting.** A toggle felt unnecessary — admins who want a specific image for a model already have the existing `meta.profile_image_url` override, which always wins. Happy to add one if you'd rather make the auto-fallback opt-in.
4. **Provider coverage scope.** Conservative on purpose — covered the brands users are most likely to enable today. Adding a provider is one regex rule + one SVG + one test row; the matcher's docstring spells out the steps.
### Additional Information
- Implementation references the existing `_safe_static_redirect_path` pattern in the same file.
- The new utility lives at `open_webui/utils/provider_logos.py` (top-level utils module, like `headers.py` / `misc.py`); it does not import from any router or model.
- Related: changelog entry "Default model profile images now redirect to a shared static path" (#24015) — same direction (cheap static redirects instead of per-request file reads).
### Screenshots or Videos
I don't have a deployed instance with an array of brand-named models to capture a clean before/after on. The behaviour is verifiable from the test matrix:
```
gpt-5 -> /static/providers/openai.svg
anthropic/claude-opus-4 -> /static/providers/claude-color.svg
gemini-2.5-pro -> /static/providers/gemini-color.svg
deepseek/deepseek-v3 -> /static/providers/deepseek-color.svg
qwen/qwen3-max -> /static/providers/qwen-color.svg
groq/llama3-70b -> /static/providers/meta-color.svg (model wins)
ollama/mistral -> /static/providers/mistral-color.svg
some-totally-unknown-id -> /static/favicon.png (unchanged)
```
### Contributor License Agreement
- [x] By submitting this pull request, I confirm that I have read and fully agree to the [Contributor License Agreement (CLA)](https://github.com/open-webui/open-webui/blob/main/CONTRIBUTOR_LICENSE_AGREEMENT), and I am providing my contributions under its terms.
---
<sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
📋 Pull Request Information
Original PR: https://github.com/open-webui/open-webui/pull/24173
Author: @ikamensh
Created: 4/27/2026
Status: ❌ Closed
Base:
dev← Head:feat/auto-provider-logos📝 Commits (1)
ad850f8feat: auto-assign provider logos to models by id📊 Changes
19 files changed (+334 additions, -0 deletions)
View changed files
📝
backend/open_webui/routers/models.py(+13 -0)➕
backend/open_webui/static/providers/LICENSE(+21 -0)➕
backend/open_webui/static/providers/README.md(+30 -0)➕
backend/open_webui/static/providers/claude-color.svg(+1 -0)➕
backend/open_webui/static/providers/cohere-color.svg(+1 -0)➕
backend/open_webui/static/providers/deepseek-color.svg(+1 -0)➕
backend/open_webui/static/providers/gemini-color.svg(+1 -0)➕
backend/open_webui/static/providers/groq.svg(+1 -0)➕
backend/open_webui/static/providers/meta-color.svg(+1 -0)➕
backend/open_webui/static/providers/mistral-color.svg(+1 -0)➕
backend/open_webui/static/providers/moonshot.svg(+1 -0)➕
backend/open_webui/static/providers/ollama.svg(+1 -0)➕
backend/open_webui/static/providers/openai.svg(+1 -0)➕
backend/open_webui/static/providers/perplexity-color.svg(+1 -0)➕
backend/open_webui/static/providers/qwen-color.svg(+1 -0)➕
backend/open_webui/static/providers/xai.svg(+1 -0)➕
backend/open_webui/static/providers/zhipu-color.svg(+1 -0)➕
backend/open_webui/test/util/test_provider_logos.py(+139 -0)➕
backend/open_webui/utils/provider_logos.py(+117 -0)📄 Description
Pull Request Checklist
dev.backend/open_webui/static/providers/from@lobehub/icons-static-svg(MIT, attribution preserved).ruff checkandruff formaton the new files.meta.profile_image_url) keeps full control. The new lookup is a tail fallback, before the existing favicon fallback.feat/auto-provider-logos, rebased ondev.feat:Changelog Entry
Description
Today, models supplied by an upstream connection (OpenAI, OpenRouter, Ollama, ...) show the grey
OIplaceholder unless an admin manually creates a model overlay row with aprofile_image_url. With this change, addinggpt-5,anthropic/claude-opus-4,gemini-2.5-pro,deepseek-v3, etc. to a connection's allowed model list shows the right provider logo immediately. Zero config.The override path is unchanged: an admin-supplied
meta.profile_image_urlalways wins. The regex match runs only when no DB image is set, and before the existing favicon fallback — so unrecognised ids behave exactly like today.Added
GET /api/v1/models/model/profile/image. When the database has noprofile_image_urlfor a model, the route now matches the model id against a small regex table and redirects to a vendored brand SVG.backend/open_webui/static/providers/(sourced from lobehub/lobe-icons, MIT —LICENSEand attributionREADME.mdincluded). Covers OpenAI, Anthropic/Claude, Google/Gemini/Gemma, DeepSeek, Qwen, Zhipu/GLM, Mistral, xAI/Grok, Meta/Llama, Cohere, Perplexity, Moonshot/Kimi, Groq, Ollama (14 files, ~32 KB total).backend/open_webui/utils/provider_logos.py— the matcher module. Documented matching philosophy and how to extend the rule set.backend/open_webui/test/util/test_provider_logos.py— 42-case pytest suite.Changed
backend/open_webui/routers/models.py::get_model_profile_image— adds the new fallback step between the DB-image lookup and the favicon fallback. No behaviour change for models that already have aprofile_image_url.Removed / Deprecated / Fixed / Security / Breaking Changes
None.
Judgment calls
A few things worth calling out for review:
CDN vs bundled SVGs. I bundled. Lobe-icons is MIT-licensed, the SVGs are tiny (~32 KB total), and self-hosting avoids a runtime third-party dependency, works offline, and prevents leaking model identifiers in browser-side requests for default avatars. If you'd rather pull at request time from
cdn.jsdelivr.net, happy to switch.Provider precedence in
host/modelids. For ids likegroq/llama3-70bthe matcher returns the Llama (model brand) logo, not Groq (inference host). Reasoning: it's still a Llama model, just hosted by Groq. The inference-host rules are placed below the model-brand rules so they only fire when no model brand is in the id (e.g. the bare idgroq). Pinned by tests.No new env var / setting. A toggle felt unnecessary — admins who want a specific image for a model already have the existing
meta.profile_image_urloverride, which always wins. Happy to add one if you'd rather make the auto-fallback opt-in.Provider coverage scope. Conservative on purpose — covered the brands users are most likely to enable today. Adding a provider is one regex rule + one SVG + one test row; the matcher's docstring spells out the steps.
Additional Information
_safe_static_redirect_pathpattern in the same file.open_webui/utils/provider_logos.py(top-level utils module, likeheaders.py/misc.py); it does not import from any router or model.Screenshots or Videos
I don't have a deployed instance with an array of brand-named models to capture a clean before/after on. The behaviour is verifiable from the test matrix:
Contributor License Agreement
🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.