diff --git a/.github/dependabot.yml b/.github/dependabot.yml index af0a8ed0e..ed93957ea 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,12 +1,26 @@ version: 2 updates: + - package-ecosystem: uv + directory: '/' + schedule: + interval: monthly + target-branch: 'dev' + - package-ecosystem: pip directory: '/backend' schedule: interval: monthly target-branch: 'dev' + + - package-ecosystem: npm + directory: '/' + schedule: + interval: monthly + target-branch: 'dev' + - package-ecosystem: 'github-actions' directory: '/' schedule: # Check for updates to GitHub Actions every week interval: monthly + target-branch: 'dev' diff --git a/.github/workflows/format-backend.yaml b/.github/workflows/format-backend.yaml index 445876697..1bcdd92c1 100644 --- a/.github/workflows/format-backend.yaml +++ b/.github/workflows/format-backend.yaml @@ -5,10 +5,18 @@ on: branches: - main - dev + paths: + - 'backend/**' + - 'pyproject.toml' + - 'uv.lock' pull_request: branches: - main - dev + paths: + - 'backend/**' + - 'pyproject.toml' + - 'uv.lock' jobs: build: @@ -17,7 +25,9 @@ jobs: strategy: matrix: - python-version: [3.11] + python-version: + - 3.11.x + - 3.12.x steps: - uses: actions/checkout@v4 @@ -25,7 +35,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: ${{ matrix.python-version }} + python-version: '${{ matrix.python-version }}' - name: Install dependencies run: | diff --git a/.github/workflows/format-build-frontend.yaml b/.github/workflows/format-build-frontend.yaml index 53d3aaa5e..9a007581f 100644 --- a/.github/workflows/format-build-frontend.yaml +++ b/.github/workflows/format-build-frontend.yaml @@ -5,10 +5,18 @@ on: branches: - main - dev + paths-ignore: + - 'backend/**' + - 'pyproject.toml' + - 'uv.lock' pull_request: branches: - main - dev + paths-ignore: + - 'backend/**' + - 'pyproject.toml' + - 'uv.lock' jobs: build: @@ -21,7 +29,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: '22' # Or specify any other version you want to use + node-version: '22' - name: Install Dependencies run: npm install diff --git a/CHANGELOG.md b/CHANGELOG.md index 3aaa79292..a11c2848e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,23 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.6.2] - 2025-04-06 + +### Added + +- 🌍 **Improved Global Language Support**: Expanded and refined translations across multiple languages to enhance clarity and consistency for international users. + +### Fixed + +- 🛠️ **Accurate Tool Descriptions from OpenAPI Servers**: External tools now use full endpoint descriptions instead of summaries when generating tool specifications—helping AI models understand tool purpose more precisely and choose the right tool more accurately in tool workflows. +- 🔧 **Precise Web Results Source Attribution**: Fixed a key issue where all web search results showed the same source ID—now each result gets its correct and distinct source, ensuring accurate citations and traceability. +- 🔍 **Clean Web Search Retrieval**: Web search now retains only results from URLs where real content was successfully fetched—improving accuracy and removing empty or broken links from citations. +- 🎵 **Audio File Upload Response Restored**: Resolved an issue where uploading audio files did not return valid responses, restoring smooth file handling for transcription and audio-based workflows. + +### Changed + +- 🧰 **General Backend Refactoring**: Multiple behind-the-scenes improvements streamline backend performance, reduce complexity, and ensure a more stable, maintainable system overall—making everything smoother without changing your workflow. + ## [0.6.1] - 2025-04-05 ### Added diff --git a/backend/open_webui/retrieval/utils.py b/backend/open_webui/retrieval/utils.py index f2d2c61de..12d48f869 100644 --- a/backend/open_webui/retrieval/utils.py +++ b/backend/open_webui/retrieval/utils.py @@ -357,7 +357,7 @@ def get_embedding_function( ): if embedding_engine == "": return lambda query, prefix=None, user=None: embedding_function.encode( - query, prompt=prefix if prefix else None + query, **({"prompt": prefix} if prefix else {}) ).tolist() elif embedding_engine in ["ollama", "openai"]: func = lambda query, prefix=None, user=None: generate_embeddings( diff --git a/backend/open_webui/routers/files.py b/backend/open_webui/routers/files.py index 22e1269e3..c30366545 100644 --- a/backend/open_webui/routers/files.py +++ b/backend/open_webui/routers/files.py @@ -122,6 +122,7 @@ def upload_file( ]: file_path = Storage.get_file(file_path) result = transcribe(request, file_path) + process_file( request, ProcessFileForm(file_id=id, content=result.get("text", "")), @@ -129,7 +130,8 @@ def upload_file( ) elif file.content_type not in ["image/png", "image/jpeg", "image/gif"]: process_file(request, ProcessFileForm(file_id=id), user=user) - file_item = Files.get_file_by_id(id=id) + + file_item = Files.get_file_by_id(id=id) except Exception as e: log.exception(e) log.error(f"Error processing file: {file_item.id}") @@ -162,11 +164,16 @@ def upload_file( @router.get("/", response_model=list[FileModelResponse]) -async def list_files(user=Depends(get_verified_user)): +async def list_files(user=Depends(get_verified_user), content: bool = Query(True)): if user.role == "admin": files = Files.get_files() else: files = Files.get_files_by_user_id(user.id) + + if not content: + for file in files: + del file.data["content"] + return files diff --git a/backend/open_webui/routers/retrieval.py b/backend/open_webui/routers/retrieval.py index 6f71e11d3..f31abd9ff 100644 --- a/backend/open_webui/routers/retrieval.py +++ b/backend/open_webui/routers/retrieval.py @@ -150,8 +150,8 @@ def get_rf( device=DEVICE_TYPE, trust_remote_code=RAG_RERANKING_MODEL_TRUST_REMOTE_CODE, ) - except: - log.error("CrossEncoder error") + except Exception as e: + log.error(f"CrossEncoder: {e}") raise Exception(ERROR_MESSAGES.DEFAULT("CrossEncoder error")) return rf @@ -174,7 +174,7 @@ class ProcessUrlForm(CollectionNameForm): url: str -class SearchForm(CollectionNameForm): +class SearchForm(BaseModel): query: str @@ -958,7 +958,7 @@ def process_file( if form_data.content: # Update the content in the file - # Usage: /files/{file_id}/data/content/update + # Usage: /files/{file_id}/data/content/update, /files/ (audio file upload pipeline) try: # /files/{file_id}/data/content/update @@ -1464,12 +1464,6 @@ async def process_web_search( log.debug(f"web_results: {web_results}") try: - collection_name = form_data.collection_name - if collection_name == "" or collection_name is None: - collection_name = f"web-search-{calculate_sha256_string(form_data.query)}"[ - :63 - ] - urls = [result.link for result in web_results] loader = get_web_loader( urls, @@ -1478,6 +1472,9 @@ async def process_web_search( trust_env=request.app.state.config.RAG_WEB_SEARCH_TRUST_ENV, ) docs = await loader.aload() + urls = [ + doc.metadata["source"] for doc in docs + ] # only keep URLs which could be retrieved if request.app.state.config.BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL: return { @@ -1494,18 +1491,26 @@ async def process_web_search( "loaded_count": len(docs), } else: - await run_in_threadpool( - save_docs_to_vector_db, - request, - docs, - collection_name, - overwrite=True, - user=user, - ) + collection_names = [] + for doc_idx, doc in enumerate(docs): + collection_name = f"web-search-{calculate_sha256_string(form_data.query + '-' + urls[doc_idx])}"[ + :63 + ] + + collection_names.append(collection_name) + + await run_in_threadpool( + save_docs_to_vector_db, + request, + [doc], + collection_name, + overwrite=True, + user=user, + ) return { "status": True, - "collection_name": collection_name, + "collection_names": collection_names, "filenames": urls, "loaded_count": len(docs), } diff --git a/backend/open_webui/utils/middleware.py b/backend/open_webui/utils/middleware.py index 62f43a702..badae9906 100644 --- a/backend/open_webui/utils/middleware.py +++ b/backend/open_webui/utils/middleware.py @@ -399,24 +399,44 @@ async def chat_web_search_handler( all_results.append(results) files = form_data.get("files", []) - if results.get("collection_name"): - files.append( - { - "collection_name": results["collection_name"], - "name": searchQuery, - "type": "web_search", - "urls": results["filenames"], - } - ) + if results.get("collection_names"): + for col_idx, collection_name in enumerate( + results.get("collection_names") + ): + files.append( + { + "collection_name": collection_name, + "name": searchQuery, + "type": "web_search", + "urls": [results["filenames"][col_idx]], + } + ) elif results.get("docs"): - files.append( - { - "docs": results.get("docs", []), - "name": searchQuery, - "type": "web_search", - "urls": results["filenames"], - } - ) + # Invoked when bypass embedding and retrieval is set to True + docs = results["docs"] + + if len(docs) == len(results["filenames"]): + # the number of docs and filenames (urls) should be the same + for doc_idx, doc in enumerate(docs): + files.append( + { + "docs": [doc], + "name": searchQuery, + "type": "web_search", + "urls": [results["filenames"][doc_idx]], + } + ) + else: + # edge case when the number of docs and filenames (urls) are not the same + # this should not happen, but if it does, we will just append the docs + files.append( + { + "docs": results.get("docs", []), + "name": searchQuery, + "type": "web_search", + "urls": results["filenames"], + } + ) form_data["files"] = files except Exception as e: diff --git a/backend/open_webui/utils/tools.py b/backend/open_webui/utils/tools.py index 60311a690..734c23e1b 100644 --- a/backend/open_webui/utils/tools.py +++ b/backend/open_webui/utils/tools.py @@ -337,7 +337,9 @@ def convert_openapi_to_tool_payload(openapi_spec): tool = { "type": "function", "name": operation.get("operationId"), - "description": operation.get("summary", "No description available."), + "description": operation.get( + "description", operation.get("summary", "No description available.") + ), "parameters": {"type": "object", "properties": {}, "required": []}, } diff --git a/backend/requirements.txt b/backend/requirements.txt index dd7c85932..499eae36d 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -54,6 +54,7 @@ elasticsearch==8.17.1 transformers sentence-transformers==3.3.1 +accelerate colbert-ai==0.2.21 einops==0.8.1 diff --git a/package-lock.json b/package-lock.json index d3de96f8e..360d02a39 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "open-webui", - "version": "0.6.1", + "version": "0.6.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "open-webui", - "version": "0.6.1", + "version": "0.6.2", "dependencies": { "@azure/msal-browser": "^4.5.0", "@codemirror/lang-javascript": "^6.2.2", diff --git a/package.json b/package.json index 9e6396015..f670644df 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "open-webui", - "version": "0.6.1", + "version": "0.6.2", "private": true, "scripts": { "dev": "npm run pyodide:fetch && vite dev --host", diff --git a/pyproject.toml b/pyproject.toml index 4c420af79..52260e45e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,6 +61,7 @@ dependencies = [ "transformers", "sentence-transformers==3.3.1", + "accelerate", "colbert-ai==0.2.21", "einops==0.8.1", diff --git a/src/app.css b/src/app.css index 4061d3b5e..86e8438f0 100644 --- a/src/app.css +++ b/src/app.css @@ -46,6 +46,14 @@ math { @apply rounded-lg; } +input::placeholder { + direction: auto; +} + +textarea::placeholder { + direction: auto; +} + .input-prose { @apply prose dark:prose-invert prose-headings:font-semibold prose-hr:my-4 prose-hr:border-gray-100 prose-hr:dark:border-gray-800 prose-p:my-0 prose-img:my-1 prose-headings:my-1 prose-pre:my-0 prose-table:my-0 prose-blockquote:my-0 prose-ul:-my-0 prose-ol:-my-0 prose-li:-my-0 whitespace-pre-line; } diff --git a/src/lib/components/channel/MessageInput.svelte b/src/lib/components/channel/MessageInput.svelte index 9ee433e30..9f495a8de 100644 --- a/src/lib/components/channel/MessageInput.svelte +++ b/src/lib/components/channel/MessageInput.svelte @@ -381,7 +381,7 @@ >
{#if files.length > 0}
diff --git a/src/lib/components/chat/Chat.svelte b/src/lib/components/chat/Chat.svelte index 2ee588985..7275baed9 100644 --- a/src/lib/components/chat/Chat.svelte +++ b/src/lib/components/chat/Chat.svelte @@ -1744,6 +1744,11 @@ history.currentId = userMessageId; await tick(); + + if (autoScroll) { + scrollToBottom(); + } + await sendPrompt(history, userPrompt, userMessageId); }; @@ -1754,6 +1759,10 @@ let userMessage = history.messages[message.parentId]; let userPrompt = userMessage.content; + if (autoScroll) { + scrollToBottom(); + } + if ((userMessage?.models ?? [...selectedModels]).length == 1) { // If user message has only one model selected, sendPrompt automatically selects it for regeneration await sendPrompt(history, userPrompt, userMessage.id); diff --git a/src/lib/components/chat/MessageInput.svelte b/src/lib/components/chat/MessageInput.svelte index 985311a98..0f42985a5 100644 --- a/src/lib/components/chat/MessageInput.svelte +++ b/src/lib/components/chat/MessageInput.svelte @@ -510,7 +510,7 @@ >
{#if files.length > 0}
@@ -821,6 +821,7 @@ {:else}