Skip to content

fix(agent): empty multimodal content becomes None - #2679

Open
1688mengdie wants to merge 1 commit into
GCWing:mainfrom
BitFun-SIG:fix/multimodal-empty-content
Open

fix(agent): empty multimodal content becomes None#2679
1688mengdie wants to merge 1 commit into
GCWing:mainfrom
BitFun-SIG:fix/multimodal-empty-content

Conversation

@1688mengdie

Copy link
Copy Markdown

Problem

An empty multimodal message (no text and no images) is converted through
impl From<Message> for AIMessage into content: Some("") - a bare empty
string sent straight to the provider, where a strict API rejects it (400) and
a permissive gateway silently bills an empty round.

Root cause

The Multimodal conversion arm has no empty-content guard. It starts content
from the text value, only appends attached-image markers when images are
present, then always wraps the result in Some(content). The Text and Mixed
arms both guard empty content (the Mixed arm returns None for empty text),
so the Multimodal arm is the only one without defence.

Fix

After building content, return None when it trims to empty. Non-empty
multimodal content (text, images, or both) is unaffected, and an image-only
message keeps its image markers. The user_multimodal constructor is left
unchanged: it returns Self and cannot reject, and serde deserialization
rebuilds the fields directly, so the conversion arm is the correct chokepoint
to defend. No new helper or configuration is introduced.

Testing

  • Test degree: tested (focused behavioral tests).
  • Added 4 behavioral cases in message.rs: empty text+images becomes None;
    non-empty text preserved; image-only kept; constructor-fed conversion.
  • cargo test -p bitfun-core --features agent-runtime --jobs 4 - message::tests
    green (9 passed); one unrelated pre-existing coordinator test still fails on
    the clean baseline.
  • AI-assisted: yes (generated with review; commands above recorded).

Closes #2676

Commit list:

  • d6f774179 fix(agent): empty multimodal content becomes None - message.rs
    Multimodal arm content -> None; adds 4 behavioral tests.

The Multimodal arm of `impl From<Message> for AIMessage` built content by
starting from the text value and only appending attached-image markers when
images were present, then always wrapped the result in `Some(content)`. A
message with empty text and no images therefore produced `Some("")` — a bare
empty string sent straight to the provider, where a strict API rejects it
(400) and a permissive gateway silently bills an empty round.

The Text and Mixed arms both guard empty content (Mixed returns None for
empty text), so the Multimodal arm was the only one without defence. The
`user_multimodal` constructor returns `Self` and cannot reject, and serde
deserialization rebuilds the fields directly, so the conversion arm is the
correct choke point to defend.

After building content, return None when it trims to empty. Non-empty
multimodal content (text, images, or both) is unaffected, and an image-only
message keeps its image markers. The constructor is left unchanged.

Test: added 4 behavioral cases (empty text+images to None, non-empty text
preserved, image-only kept, constructor-fed conversion) in message.rs;
agentic::core::message::tests pass.
AI: generated with review; verified with `cargo test -p bitfun-core
--features agent-runtime --jobs 4` (message.rs tests green; one unrelated
pre-existing coordinator test still fails on the clean baseline).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: empty multimodal messages are sent to the provider as a bare empty string

1 participant