diff --git a/backend/open_webui/main.py b/backend/open_webui/main.py index 2ef0cf5496..bf2016f6c8 100644 --- a/backend/open_webui/main.py +++ b/backend/open_webui/main.py @@ -140,7 +140,7 @@ from open_webui.models.chats import ChatForm, Chats from open_webui.models.config import Config from open_webui.models.functions import Functions from open_webui.models.messages import Messages -from open_webui.models.models import Models +from open_webui.models.models import Models, normalize_model_tags from open_webui.models.users import Users from open_webui.routers import ( analytics, @@ -890,19 +890,17 @@ async def get_models(request: Request, refresh: bool = False, user=Depends(get_v models = await get_filtered_models(models, user) for model in models: + info = model.get('info') if isinstance(model.get('info'), dict) else {} + meta = info.get('meta') if isinstance(info.get('meta'), dict) else {} + # Remove profile image URL to reduce payload size - if model.get('info', {}).get('meta', {}).get('profile_image_url'): - model['info']['meta'].pop('profile_image_url', None) + meta.pop('profile_image_url', None) - try: - model_tags = [tag.get('name') for tag in model.get('info', {}).get('meta', {}).get('tags', [])] - tags = [tag.get('name') for tag in model.get('tags', [])] + if 'tags' in meta: + meta['tags'] = normalize_model_tags(meta['tags']) - tags = list(set(model_tags + tags)) - model['tags'] = [{'name': tag} for tag in tags] - except Exception as e: - log.debug('Error processing model tags: %s', e) - model['tags'] = [] + tags = normalize_model_tags(meta.get('tags')) + normalize_model_tags(model.get('tags')) + model['tags'] = list({tag['name']: tag for tag in tags}.values()) model_order_list = await Config.get('ui.model_order_list') if model_order_list: @@ -1857,6 +1855,7 @@ async def chat_completion( generate_chat_completions = chat_completion generate_chat_completion = chat_completion + @app.post('/api/v1/chats/{id}/messages/{message_id}/resolve') async def resolve_chat_message_tool_call( request: Request, diff --git a/backend/open_webui/models/models.py b/backend/open_webui/models/models.py index 708eb2b7e5..0f3b731ea4 100755 --- a/backend/open_webui/models/models.py +++ b/backend/open_webui/models/models.py @@ -3,7 +3,7 @@ from __future__ import annotations import logging import time from copy import deepcopy -from typing import Any, Optional +from typing import Any from open_webui.internal.db import Base, JSONField, get_async_db_context from open_webui.models.access_grants import AccessGrantModel, AccessGrants @@ -13,7 +13,6 @@ from open_webui.utils.misc import json_text_variants from open_webui.utils.validate import validate_profile_image_url from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator from sqlalchemy import BigInteger, Boolean, Column, String, Text, cast, delete, func, or_, select, update -from sqlalchemy.dialects.postgresql import JSONB from sqlalchemy.ext.asyncio import AsyncSession log = logging.getLogger(__name__) @@ -23,6 +22,18 @@ log = logging.getLogger(__name__) _warned_profile_urls: set[str] = set() +def normalize_model_tags(tags: Any) -> list[dict[str, str]]: + if not isinstance(tags, list): + return [] + + normalized = [] + for tag in tags: + name = tag.get('name') if isinstance(tag, dict) else tag + if isinstance(name, str) and name.strip(): + normalized.append({'name': name.strip()}) + return normalized + + def strip_extracted_content_from_model_knowledge(knowledge: Any) -> Any: """Drop duplicated extracted text from ModelMeta.knowledge.""" if not isinstance(knowledge, list): @@ -99,15 +110,7 @@ class ModelMeta(BaseModel): @classmethod def normalize_tags(cls, data): if isinstance(data, dict) and 'tags' in data: - raw_tags = data['tags'] - if isinstance(raw_tags, list): - normalized = [] - for tag in raw_tags: - if isinstance(tag, str): - normalized.append({'name': tag}) - elif isinstance(tag, dict) and 'name' in tag: - normalized.append(tag) - data['tags'] = normalized + data['tags'] = normalize_model_tags(data['tags']) return data diff --git a/src/lib/apis/index.ts b/src/lib/apis/index.ts index 65046b4004..04d8323177 100644 --- a/src/lib/apis/index.ts +++ b/src/lib/apis/index.ts @@ -1,5 +1,6 @@ import { WEBUI_BASE_URL } from '$lib/constants'; import { convertOpenApiToToolPayload } from '$lib/utils'; +import { normalizeTags } from '$lib/utils/tags'; import { getOpenAIModelsDirect } from './openai'; const TOOL_SERVER_FETCH_TIMEOUT = 10000; @@ -141,8 +142,8 @@ export const getModels = async ( } } - const tags = apiConfig.tags; - if (tags) { + const tags = normalizeTags(apiConfig.tags); + if (tags.length > 0) { for (const model of models) { model.tags = tags; } diff --git a/src/lib/components/AddConnectionModal.svelte b/src/lib/components/AddConnectionModal.svelte index 2c7ed22a87..55cda09968 100644 --- a/src/lib/components/AddConnectionModal.svelte +++ b/src/lib/components/AddConnectionModal.svelte @@ -18,6 +18,7 @@ import Spinner from '$lib/components/common/Spinner.svelte'; import XMark from '$lib/components/icons/XMark.svelte'; import Textarea from './common/Textarea.svelte'; + import { normalizeTags } from '$lib/utils/tags'; export let onSubmit: Function = () => {}; export let onDelete: Function = () => {}; @@ -249,7 +250,7 @@ : ''; enable = connection.config?.enable ?? true; - tags = connection.config?.tags ?? []; + tags = normalizeTags(connection.config?.tags); prefixId = connection.config?.prefix_id ?? ''; passthroughParams = Array.isArray(connection.config?.passthrough_params) ? connection.config.passthrough_params.join(', ') diff --git a/src/lib/utils/tags.ts b/src/lib/utils/tags.ts new file mode 100644 index 0000000000..12b626087e --- /dev/null +++ b/src/lib/utils/tags.ts @@ -0,0 +1,17 @@ +export type Tag = { name: string }; + +const getTagName = (tag: unknown) => { + if (typeof tag === 'string') { + return tag; + } + + if (typeof tag === 'object' && tag !== null && 'name' in tag) { + return (tag as { name?: unknown }).name; + } +}; + +export const normalizeTags = (tags: unknown): Tag[] => + (Array.isArray(tags) ? tags : []) + .map(getTagName) + .filter((name): name is string => typeof name === 'string' && name.trim() !== '') + .map((name) => ({ name: name.trim() }));