mirror of
https://github.com/open-webui/open-webui.git
synced 2026-09-12 23:02:35 +00:00
fix: stop base64 images in tool results from reaching the model as text (#29665)
When a tool returns a base64 image, Open WebUI only moves it out of the model's context if the entire result is that image, or if the tool is MCP. An image sitting inside a returned object or a list was serialised into the tool message instead, so a single screenshot could cost hundreds of thousands of tokens, push out the rest of the conversation and leave the model answering from garbage. Any string in a tool result that is exactly one image data URI is now moved into the result's files wherever it sits in the structure, and replaced with a short marker. The model gets the image as an attachment rather than as text, and it renders for the user instead of being dropped. Detection is deliberately limited to values that are entirely a data URI. Scanning inside longer strings was tried and abandoned: a payload wrapped over several lines, or one followed by text, gets cut short, and shipping a truncated image is worse than the bloat because the provider rejects the whole request. This also fixes the OpenAPI branch removing entries from the list it was iterating over, which skipped every second data URI and left it in the model's context. Fixes #29208
This commit is contained in:
parent
6f55514c5c
commit
afda094544
1 changed files with 24 additions and 2 deletions
|
|
@ -976,6 +976,25 @@ async def apply_source_context_to_messages(
|
|||
)
|
||||
|
||||
|
||||
BASE64_IMAGE_DATA_URI_RE = re.compile(r'data:image/[a-zA-Z0-9.+-]+;base64,[A-Za-z0-9+/]+={0,2}', re.IGNORECASE)
|
||||
|
||||
|
||||
def extract_base64_images(value: Any, files: list) -> Any:
|
||||
"""Move base64 image data URIs out of a tool result so they do not reach the model as text."""
|
||||
if isinstance(value, str):
|
||||
if BASE64_IMAGE_DATA_URI_RE.fullmatch(value):
|
||||
files.append({'type': 'image', 'url': value})
|
||||
return '[image]'
|
||||
return value
|
||||
if isinstance(value, dict):
|
||||
return {key: extract_base64_images(item, files) for key, item in value.items()}
|
||||
if isinstance(value, list):
|
||||
return [extract_base64_images(item, files) for item in value]
|
||||
if isinstance(value, tuple):
|
||||
return tuple(extract_base64_images(item, files) for item in value)
|
||||
return value
|
||||
|
||||
|
||||
async def process_tool_result(
|
||||
request,
|
||||
tool_function_name,
|
||||
|
|
@ -1161,8 +1180,9 @@ async def process_tool_result(
|
|||
tool_response.append(resource.get('uri'))
|
||||
tool_result = tool_response[0] if len(tool_response) == 1 else tool_response
|
||||
else: # OpenAPI
|
||||
for item in tool_result:
|
||||
if isinstance(item, str) and item.startswith('data:'):
|
||||
# Images are left to extract_base64_images below, which attaches them so the model can see them.
|
||||
for item in list(tool_result):
|
||||
if isinstance(item, str) and item.startswith('data:') and not BASE64_IMAGE_DATA_URI_RE.fullmatch(item):
|
||||
tool_result_files.append(
|
||||
{
|
||||
'type': 'data',
|
||||
|
|
@ -1171,6 +1191,8 @@ async def process_tool_result(
|
|||
)
|
||||
tool_result.remove(item)
|
||||
|
||||
tool_result = extract_base64_images(tool_result, tool_result_files)
|
||||
|
||||
if isinstance(tool_result, list):
|
||||
tool_result = {'results': tool_result}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue