Skip to content

Commit 72ac660

Browse files
Merge branch 'open-webui:dev' into dev
2 parents 9d41cd6 + d9c8de9 commit 72ac660

7 files changed

Lines changed: 1083 additions & 1080 deletions

File tree

‎.github/pull_request_template.md‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@ Link the maintainer's request for this PR, or state that the change is limited t
1717

1818
## Checklist
1919

20+
- [ ] I have read and I understand the [contribution policy](https://docs.openwebui.com/contributing/#submit-code).
2021
- [ ] This PR targets the `dev` branch.
2122
- [ ] This PR links to a well-described, confirmed Issue or active Discussion: `Closes #___` / `Relates to #___`.
2223
- [ ] A maintainer explicitly asked me to open this PR, or this PR only updates i18n/localization.
2324
- [ ] The change is one logical unit with no unrelated commits.
2425
- [ ] I matched nearby code patterns and avoided unnecessary new settings, abstractions, or dependencies.
2526
- [ ] I manually tested the changed workflow and any nearby behavior that could be affected.
27+
- [ ] I have not added or rewritten automated tests, fixtures, snapshots, or testing infrastructure unless a maintainer explicitly requested them.
2628
- [ ] I updated relevant docs, including the [Open WebUI Docs Repository](https://github.com/open-webui/docs), if needed.
2729
- [ ] I added screenshots for UI changes, and a recording when motion or interaction matters.
2830
- [ ] I reviewed any AI-generated code before submitting it.
@@ -47,9 +49,11 @@ Use one of the following prefixes:
4749

4850
Describe the change, the problem it solves, and the impact on users.
4951

50-
## Testing
52+
## Verification
5153

52-
List the exact manual checks you ran. Include commands, setup details, screenshots, or recordings where helpful.
54+
Describe how you reproduced the problem and manually checked the behavior before and after the change. Include exact steps, setup details, and relevant logs, screenshots, or recordings. Report results from relevant existing checks and anything you could not verify.
55+
56+
Do not add or rewrite automated tests unless a maintainer explicitly requests them. Tests that repeat an implementation's assumptions can pass while preserving the same mistake; maintainers determine the regression coverage needed. Do not remove, disable, or weaken existing tests to make the change pass.
5357

5458
## Changelog Entry
5559

‎backend/open_webui/env.py‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1043,7 +1043,8 @@ def _parse_ssl_env(value: str) -> 'bool | _ssl.SSLContext':
10431043
ENABLE_API_OUTLET_FILTERS = os.getenv('ENABLE_API_OUTLET_FILTERS', 'True').lower() == 'true'
10441044

10451045
# Opt in to CPython's in-place string append optimization for streamed responses.
1046-
# Disabled by default: allocation failure in the fast path can erase accumulated text.
1046+
# Off by default for a staged rollout. Only a host already out of memory can lose
1047+
# text here; the default path (a full copy per chunk) raises there too.
10471048
ENABLE_CHAT_RESPONSE_STREAM_INPLACE_APPEND = os.getenv('ENABLE_CHAT_RESPONSE_STREAM_INPLACE_APPEND', 'False').lower() == 'true'
10481049

10491050
# When enabled, uses a hardcoded extension-to-MIME dictionary as a last-resort

‎backend/open_webui/routers/openai.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1417,8 +1417,8 @@ def convert_to_responses_payload(payload: dict) -> dict:
14171417
converted_tool['description'] = func['description']
14181418
if 'parameters' in func:
14191419
converted_tool['parameters'] = func['parameters']
1420-
if 'strict' in func:
1421-
converted_tool['strict'] = func['strict']
1420+
# Responses defaults strict to true, Chat Completions to false
1421+
converted_tool['strict'] = func.get('strict', False)
14221422
converted_tools.append(converted_tool)
14231423
else:
14241424
# Already in correct format or unknown format, pass through

‎backend/open_webui/tools/knowledge_fs.py‎

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@
77
Re-exported through builtin.py for consistent imports.
88
"""
99

10+
import asyncio
1011
import contextvars
1112
import logging
1213
import re
1314
import shlex
1415
import time
16+
from collections.abc import Callable
1517
from contextlib import contextmanager
1618
from typing import Optional
1719

@@ -727,6 +729,10 @@ async def _kb_tail(
727729
return result
728730

729731

732+
def _match_lines(content: str, matches: Callable[[str], bool]) -> list[tuple[int, str]]:
733+
return [(i, line) for i, line in enumerate(content.split('\n'), 1) if matches(line)]
734+
735+
730736
async def _kb_grep(
731737
args: list[str], flags: set[str], user: dict, model_knowledge: list[dict] | None, piped_input: str | None = None
732738
) -> str:
@@ -752,17 +758,14 @@ async def _kb_grep(
752758
count_only = 'c' in flags
753759
use_regex = 'E' in flags
754760

755-
_matches, err = build_matcher(pattern, case_insensitive, use_regex)
761+
_matches, err = await asyncio.to_thread(build_matcher, pattern, case_insensitive, use_regex)
756762
if err:
757763
return err
758764

759765
# Grep on piped input
760766
if piped_input is not None:
761-
lines = piped_input.split('\n')
762-
matched = []
763-
for i, line in enumerate(lines, 1):
764-
if _matches(line):
765-
matched.append(f'{i}: {line}')
767+
found = await asyncio.to_thread(_match_lines, piped_input, _matches)
768+
matched = [f'{i}: {line}' for i, line in found]
766769
if count_only:
767770
return str(len(matched))
768771
if filenames_only:
@@ -778,11 +781,8 @@ async def _kb_grep(
778781
elif 'error' in resolved:
779782
return resolved['error']
780783
else:
781-
lines = resolved['content'].split('\n')
782-
matched = []
783-
for i, line in enumerate(lines, 1):
784-
if _matches(line):
785-
matched.append(f'{i}: {line}')
784+
found = await asyncio.to_thread(_match_lines, resolved['content'], _matches)
785+
matched = [f'{i}: {line}' for i, line in found]
786786

787787
if count_only:
788788
return f'{resolved["id"]} {resolved["filename"]}: {len(matched)}'
@@ -833,11 +833,7 @@ async def _kb_grep(
833833
if not content:
834834
continue
835835

836-
lines = content.split('\n')
837-
file_matches = []
838-
for i, line in enumerate(lines, 1):
839-
if _matches(line):
840-
file_matches.append((i, line))
836+
file_matches = await asyncio.to_thread(_match_lines, content, _matches)
841837

842838
if file_matches:
843839
files_with_matches.append(file_info)

‎backend/open_webui/utils/middleware.py‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ def append_to_text_field(item: dict, key: str, value: str) -> None:
324324
return
325325

326326
# Opt-in: dropping the dict's reference lets CPython extend an unshared str
327-
# in place. An allocation failure can leave the field empty.
327+
# in place. Only a host that is already out of memory can leave the field empty.
328328
text = item[key]
329329
item[key] = ''
330330
text += value
@@ -2888,7 +2888,8 @@ async def process_chat_payload(request, form_data, user, metadata, model):
28882888
# that reject empty content blocks (e.g. AWS Bedrock ConverseStream).
28892889
if not prompt or not prompt.strip():
28902890
fallback = ', '.join([s.name for s in available_skills] + [s['name'] for s in terminal_skills])
2891-
if fallback:
2891+
# Attachment-only messages keep their empty text, same as on models without skills.
2892+
if fallback and not (metadata.get('user_message') or {}).get('files'):
28922893
set_last_user_message_content(fallback, form_data['messages'])
28932894
prompt = fallback
28942895
# TODO: re-enable URL extraction from prompt

0 commit comments

Comments
 (0)