-
Notifications
You must be signed in to change notification settings - Fork 161
fix(backends): reject empty user content before sending chat request #1632
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
9179e43
d93723f
6142775
cb6a106
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -37,6 +37,7 @@ | |||||||||||||||||||||||||||
| DEFAULT_CHUNK_TIMEOUT, | ||||||||||||||||||||||||||||
| ClientCache, | ||||||||||||||||||||||||||||
| get_current_event_loop, | ||||||||||||||||||||||||||||
| has_user_content, | ||||||||||||||||||||||||||||
| merge_provider_fields, | ||||||||||||||||||||||||||||
| message_to_openai_message, | ||||||||||||||||||||||||||||
| messages_to_docs, | ||||||||||||||||||||||||||||
|
|
@@ -1076,6 +1077,8 @@ async def generate_from_chat_context( | |||||||||||||||||||||||||||
| cannot be downloaded or decoded. | ||||||||||||||||||||||||||||
| ValueError: If a message contains an `AudioBlock` or `AudioUrlBlock`; | ||||||||||||||||||||||||||||
| Ollama does not support audio input. | ||||||||||||||||||||||||||||
| ValueError: If no user-role message has non-whitespace text, | ||||||||||||||||||||||||||||
| images, audio, or documents. | ||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||
| # Start by awaiting any necessary computation. | ||||||||||||||||||||||||||||
| await self.do_generate_walk(action) | ||||||||||||||||||||||||||||
|
|
@@ -1090,6 +1093,17 @@ async def generate_from_chat_context( | |||||||||||||||||||||||||||
| messages: list[Message] = self.formatter.to_chat_messages(linearized_context) | ||||||||||||||||||||||||||||
| # Add the final message. | ||||||||||||||||||||||||||||
| messages.extend(self.formatter.to_chat_messages([action])) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| # Issue #1597: refuse to send an empty user prompt; see | ||||||||||||||||||||||||||||
| # `has_user_content` for why whitespace-only text still counts as empty. | ||||||||||||||||||||||||||||
| if not has_user_content(messages): | ||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||||||||||||||||||||||||
| raise ValueError( | ||||||||||||||||||||||||||||
| "Refusing to call the model: no user-role content in the assembled " | ||||||||||||||||||||||||||||
| "conversation. This usually means a stateless context (e.g. " | ||||||||||||||||||||||||||||
| "SimpleContext) was combined with an empty or whitespace-only " | ||||||||||||||||||||||||||||
| "action; recorded turns are not forwarded to the model. See " | ||||||||||||||||||||||||||||
| "issue #1597." | ||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||
|
Comment on lines
+1100
to
+1106
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same note as the other two backends — drop the issue number from the user-facing message.
Suggested change
|
||||||||||||||||||||||||||||
| # construct the conversation from our messages, adding a system prompt at the first message if one was provided. | ||||||||||||||||||||||||||||
| conversation: list[dict] = [] | ||||||||||||||||||||||||||||
| # We use system prompt None/empty-string semantics in a way that is consistent with Hugging Face and other libraries. | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -45,6 +45,7 @@ | |||||||||||||||||||||||||||
| chat_completion_delta_merge, | ||||||||||||||||||||||||||||
| extract_model_tool_requests, | ||||||||||||||||||||||||||||
| get_current_event_loop, | ||||||||||||||||||||||||||||
| has_user_content, | ||||||||||||||||||||||||||||
| is_vllm_server_with_structured_output, | ||||||||||||||||||||||||||||
| message_to_openai_message, | ||||||||||||||||||||||||||||
| messages_to_docs, | ||||||||||||||||||||||||||||
|
|
@@ -1366,6 +1367,10 @@ async def generate_from_chat_context( | |||||||||||||||||||||||||||
| Returns: | ||||||||||||||||||||||||||||
| tuple[ModelOutputThunk[C], Context]: A thunk holding the (lazy) model output | ||||||||||||||||||||||||||||
| and an updated context that includes `action` and the new output. | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| Raises: | ||||||||||||||||||||||||||||
| ValueError: If no user-role message has non-whitespace text, | ||||||||||||||||||||||||||||
| images, audio, or documents. | ||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||
| await self.do_generate_walk(action) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
|
@@ -1401,6 +1406,17 @@ async def _generate_from_chat_context_standard( | |||||||||||||||||||||||||||
| # ALoraRequirement may arrive here when no adapter is registered; | ||||||||||||||||||||||||||||
| # _generate is responsible for logging a warning in that case. | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| # Issue #1597: refuse to send an empty user prompt; see | ||||||||||||||||||||||||||||
| # `has_user_content` for why whitespace-only text still counts as empty. | ||||||||||||||||||||||||||||
| if not has_user_content(messages): | ||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This error reaches callers through the public |
||||||||||||||||||||||||||||
| raise ValueError( | ||||||||||||||||||||||||||||
| "Refusing to call the model: no user-role content in the assembled " | ||||||||||||||||||||||||||||
| "conversation. This usually means a stateless context (e.g. " | ||||||||||||||||||||||||||||
| "SimpleContext) was combined with an empty or whitespace-only " | ||||||||||||||||||||||||||||
| "action; recorded turns are not forwarded to the model. See " | ||||||||||||||||||||||||||||
| "issue #1597." | ||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||
|
Comment on lines
+1412
to
+1418
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Minor: the raised message cites "issue #1597" directly. Worth dropping that from the user-facing string — a library consumer hitting this at runtime has no use for an internal tracking number. Keeping it in the comment above is fine.
Suggested change
|
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| conversation: list[dict] = [] | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| # Resolve any audio URLs off-thread so the sync serializer below hits the cache | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -618,3 +618,32 @@ def build_tool_calls(output: ModelOutputThunk) -> list[ToolCallDict] | None: | |||||||||||||||||||||||||||||||||||||||||||||||||||
| tool_calls.append(tool_call) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| return tool_calls | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| def has_user_content(messages: list[Message]) -> bool: | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Whether the assembled conversation carries real user-role content. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| Issue #1597: `SimpleContext` intentionally discards recorded turns from | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| `view_for_generation()`, so a caller who chains `.add(...)` and then | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| passes an empty action would otherwise hit the model with no user-role | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| content at all. Some chat models (e.g. Granite 4.2, see #1587) spin on | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| empty prompts and burn tokens silently. Whitespace-only text counts as | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| empty; images, audio, and documents count as content. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| Args: | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| messages: The chat messages about to be sent to the model. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| Returns: | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| True if any user-role message has non-whitespace text, images, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| audio, or documents; False otherwise. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+624
to
+639
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The new
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| return any( | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| m.role == "user" | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| and ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| (m.content and m.content.strip()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| or m.images | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| or m.audio | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| or getattr(m, "_docs", None) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| for m in messages | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same note as the OpenAI backend — drop the issue number from the user-facing message.