fix(openwebui): bound calls and force prose synthesis

This commit is contained in:
Mikei386
2026-08-24 08:33:44 +02:00
parent 4d4eb0b8d8
commit 50c37b09bb
6 changed files with 79 additions and 8 deletions
+73 -3
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
"""Make Open WebUI finish with an answer when its internal tool budget is used.
Open WebUI 0.9.5 otherwise ends the request with only
The pinned Open WebUI revision otherwise ends the request with only
``Tool-call limit reached``. The patch is deliberately assertion-based: an
upstream source change makes the image build fail instead of silently applying
the modification at the wrong location.
@@ -21,6 +21,57 @@ patch_marker = "The tool budget for this turn is exhausted. Do not call"
if patch_marker in source:
raise SystemExit("Open WebUI tool-finalization patch is already present")
needle = """ tool_call_iterations = 0
max_tool_call_iterations = getattr(
"""
replacement = """ tool_call_iterations = 0
# Open WebUI counts batches, while one model turn may request many
# functions in parallel. Bound actual executions as well so a small
# local model cannot expand eight rounds into dozens of API calls.
tool_call_executions = 0
max_tool_call_executions = 12
max_tool_call_iterations = getattr(
"""
if source.count(needle) != 1:
raise SystemExit(
f"expected exactly one Open WebUI tool counter anchor, found {source.count(needle)}"
)
source = source.replace(needle, replacement)
needle = """ response_tool_calls = tool_calls.pop(0)
# Append function_call items for each tool call
"""
replacement = """ response_tool_calls = tool_calls.pop(0)
remaining_tool_calls = max(
0, max_tool_call_executions - tool_call_executions
)
skipped_tool_calls = response_tool_calls[remaining_tool_calls:]
response_tool_calls = response_tool_calls[:remaining_tool_calls]
if skipped_tool_calls:
skipped_ids = {call.get('id', '') for call in skipped_tool_calls}
# Responses API streaming may already have exposed all calls
# in `output`. Remove deliberately skipped calls so the next
# completion never receives an orphan function call.
output[:] = [
item
for item in output
if not (
item.get('type') == 'function_call'
and item.get('call_id', '') in skipped_ids
)
]
tool_call_executions += len(response_tool_calls)
# Append function_call items for each tool call
"""
if source.count(needle) != 1:
raise SystemExit(
f"expected exactly one Open WebUI tool batch anchor, found {source.count(needle)}"
)
source = source.replace(needle, replacement)
needle = """ res = await generate_chat_completion(
request,
new_form_data,
@@ -35,12 +86,16 @@ replacement = """ # The upstream loop otherwise stops wit
# result, but expose no tools to the model and explicitly require
# a useful, evidence-bounded answer.
force_final_response = (
max_tool_call_iterations is not None
and tool_call_iterations >= max_tool_call_iterations
(
max_tool_call_iterations is not None
and tool_call_iterations >= max_tool_call_iterations
)
or tool_call_executions >= max_tool_call_executions
)
if force_final_response:
new_form_data.pop('tools', None)
new_form_data.pop('tool_ids', None)
new_form_data.pop('tool_choice', None)
final_metadata = dict(metadata)
final_metadata['tools'] = {}
final_metadata['tool_ids'] = []
@@ -56,6 +111,21 @@ replacement = """ # The upstream loop otherwise stops wit
new_form_data['messages'],
append=True,
)
# A final user-role instruction is deliberately stronger
# than another system suffix after a long tool-call pattern.
# It is request-local and is not added to the saved chat.
new_form_data['messages'].append(
{
'role': 'user',
'content': (
'The research phase is finished. Answer my original '
'question now in normal prose. Do not output XML, JSON, '
'function names, tool_call blocks, or requests for more '
'files. Use the available evidence, mention uncertainty, '
'and provide a useful final conclusion.'
),
}
)
res = await generate_chat_completion(
request,