mirror of
https://github.com/open-webui/open-webui.git
synced 2026-11-14 05:02:01 -06:00
fix: assistant messages with only tool calls are sent with an empty string instead of null (#34265)
When an assistant message has tool calls but no text, the OpenAI Chat Completions format uses `content: null`, since content is optional next to tool calls and OpenAI's own API returns null there. Open WebUI sent an empty string in two places: when replaying earlier tool calls from the chat history, and when converting requests from clients that use its Anthropic-compatible endpoint. For Claude models, LiteLLM 1.81.14 through 1.108.x replaces that empty string with "[System: Empty message content sanitised to satisfy protocol]", and the model then repeats that line in its replies (LiteLLM 1.109 and later leave the empty text out instead). These messages now go out with null content, which OpenAI and every provider tested on OpenRouter (Gemini, Claude, DeepSeek, Qwen, Mistral, Llama, Kimi and Grok) accept. Fixes #34264
This commit is contained in:
@@ -364,7 +364,7 @@ def convert_anthropic_payload_to_chat_completions(
|
||||
if openai_content:
|
||||
msg_dict['content'] = _finalize_chat_completions_content(openai_content)
|
||||
else:
|
||||
msg_dict['content'] = ''
|
||||
msg_dict['content'] = None
|
||||
msg_dict['tool_calls'] = tool_calls
|
||||
messages.append(msg_dict)
|
||||
elif openai_content or role == 'assistant':
|
||||
|
||||
@@ -500,7 +500,7 @@ async def _review_memory(
|
||||
role = message.get('role', '')
|
||||
content = message.get('content', '')
|
||||
if not isinstance(content, str):
|
||||
content = get_content_from_message(message)
|
||||
content = get_content_from_message(message) or ''
|
||||
content = content.strip()
|
||||
if role not in {'user', 'assistant'} or not content:
|
||||
continue
|
||||
|
||||
@@ -1029,7 +1029,7 @@ async def chat_completion_tools_handler(
|
||||
|
||||
recent_messages = messages[-4:] if len(messages) > 4 else messages
|
||||
chat_history = '\n'.join(
|
||||
f'{message["role"].upper()}: """{get_content_from_message(message)}"""' for message in recent_messages
|
||||
f'{message["role"].upper()}: """{get_content_from_message(message) or ""}"""' for message in recent_messages
|
||||
)
|
||||
|
||||
prompt = f'History:\n{chat_history}\nQuery: {user_message}' if chat_history else f'Query: {user_message}'
|
||||
|
||||
@@ -223,7 +223,7 @@ def get_message_list(messages_map, message_id):
|
||||
|
||||
|
||||
def get_messages_content(messages: list[dict]) -> str:
|
||||
return '\n'.join([f'{message["role"].upper()}: {get_content_from_message(message)}' for message in messages])
|
||||
return '\n'.join([f'{message["role"].upper()}: {get_content_from_message(message) or ""}' for message in messages])
|
||||
|
||||
|
||||
def get_last_user_message_item(messages: list[dict]) -> dict | None:
|
||||
@@ -477,7 +477,7 @@ def convert_output_to_messages(
|
||||
|
||||
message = {
|
||||
'role': 'assistant',
|
||||
'content': '\n'.join(pending_content) if pending_content else '',
|
||||
'content': '\n'.join(pending_content) if pending_content else (None if pending_tool_calls else ''),
|
||||
**({'tool_calls': pending_tool_calls} if pending_tool_calls else {}),
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user