zylon-ai/private-gpt · error · ValueError

Message content cannot be empty: {message}

Error message

Message content cannot be empty: {message}

What it means

The ChatBody validator iterates all messages and rejects any message whose content is empty (empty string, empty list, or None). Unlike sampling params, empty content is not auto-corrected — it is a client error, surfaced as ValidationError/422 with the offending message embedded in the error text.

Source

Thrown at private_gpt/server/chat/chat_models.py:317

        if self.presence_penalty and self.presence_penalty < 0:
            self.presence_penalty = None

        if self.frequency_penalty and self.frequency_penalty < 0:
            self.frequency_penalty = None

        if self.seed and self.seed < 0:
            self.seed = None

        if self.max_tokens is not None and self.max_tokens <= 0:
            self.max_tokens = None

        if not self.messages:
            raise ValueError("Messages cannot be empty")

        for message in self.messages:
            if not message.content:
                raise ValueError(f"Message content cannot be empty: {message}")
            if isinstance(message.content, list):
                for block in message.content:
                    if block is None:
                        raise ValueError(f"Block cannot be None: {message}")

        if self.messages[-1].role not in self._valid_last_message_roles:
            raise ValueError(
                f"Last message role must be one of {self._valid_last_message_roles}, but got {self.messages[-1].role}"
            )

        # Check tools and tool choice
        if self.tools and self.tool_choice and self.tool_choice.type == "tool":
            if not self.tools:
                raise ValueError("Tool choice is set, but no tools are provided.")
            if self.tool_choice.name not in [tool.name for tool in self.tools]:
                raise ValueError(
                    f"Tool choice '{self.tool_choice}' is not in the provided tools."
                )

View on GitHub (pinned to 4a030776a3)

Solutions

  1. Drop empty messages client-side before sending (filter m for m.content).
  2. For tool-call-only assistant turns, set content to a non-empty placeholder or a content block list that is non-empty.
  3. Add a client-side assert that every message has truthy content.

Example fix

# before
messages = history + [{"role": "user", "content": user_text}]  # user_text may be ""

# after
messages = [m for m in history if m.get("content")] 
if user_text:
    messages.append({"role": "user", "content": user_text})
Defensive patterns

Strategy: validation

Validate before calling

clean = [m for m in messages if m.get('content')]
assert clean, 'refusing to send: all messages empty'

Type guard

def all_content_nonempty(msgs: list[dict]) -> bool:
    return all(bool(m.get('content')) for m in msgs)

Try / catch

except ValidationError as e:
    if 'content cannot be empty' in str(e):
        messages = [m for m in messages if m.get('content')]
        resp = client.create(messages=messages, **rest)
    else:
        raise

Prevention

When it happens

Trigger: A message like {"role":"user","content":""} or {"role":"assistant","content":[]} in messages; clients that append an empty placeholder message before streaming; trimming logic that empties content instead of dropping the message.

Common situations: Chat UIs that submit before the user types anything; assistant messages serialized with content removed when tool_calls-only messages are converted; upstream services forwarding pre-emptied content fields.

Related errors


AI-assisted analysis of zylon-ai/private-gpt@4a030776a3 (2026-08-15). Data as JSON: /api/errors/fade166faf2ab107. Report an issue: GitHub.