Skip to content

[Bug]: empty text messages are fabricated into placeholder tokens before reaching the model #2675

Description

@1688mengdie

Problem

When an empty text message (for example Message::user("")) is converted through
impl From<Message> for AIMessage, the conversion's Text arm fabricates placeholder
content to avoid sending an empty value to the provider:

  • empty user text becomes "(empty message)"
  • empty system text becomes "You are a helpful assistant."
  • any other empty text becomes a single space " "

The model then treats these fabricated strings as a real user utterance or as a
system directive, which pollutes the conversation context.

Root cause

The defensive "avoid empty content" intent is implemented by injecting a synthetic
non-empty token rather than by returning no content. This is the opposite of the
downstream converters' own empty-content handling: openai/gemini filter out empty
content, and anthropic skips an empty system message - but a non-empty fabricated
string bypasses those filters. The Mixed arm already handles empty text correctly by
producing None, so the Text arm did not need to invent its own behaviour.

Repro

  1. Convert Message::user(String::new()) via AIMessage::from(...).
  2. Observe the resulting content is Some("(empty message)") instead of None.

Impact

Synthetic instructions and user utterances reach the model for what is actually an
empty message, corrupting the context. The conversion layer is a single chokepoint
that every Message -> AIMessage path flows through.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions