mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-21 15:55:43 +00:00
fix(moa): scope the non-text placeholder to structured content only
Follow-up to the cherry-picked empty-user-turn drop: the placeholder
introduced in 8582f35d9 fired for whitespace-only STRING turns too
(content=' ' flattens to non-stripping text but isn't in the
(None, '', []) exclusion set), fabricating an attachment note for a turn
that carried nothing. Gate the placeholder on isinstance(content, list)
so only genuinely structured (e.g. image-only) turns get it; empty and
whitespace-only string turns now fall through to the drop path.
Edge cases verified: trailing empty user turn still ends the view on the
synthetic advisory marker; an all-empty transcript degenerates to [].
This commit is contained in:
parent
b4c2c4f922
commit
b013ed03e5
2 changed files with 28 additions and 2 deletions
|
|
@ -491,7 +491,7 @@ def _reference_messages(messages: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|||
if role == "system":
|
||||
continue
|
||||
if role == "user":
|
||||
if not text.strip() and content not in (None, "", []):
|
||||
if not text.strip() and isinstance(content, list) and content:
|
||||
# Structured content with no extractable text (e.g. an
|
||||
# image-only turn). Emitting an empty user message would be
|
||||
# dropped/rejected by strict providers (Anthropic 400s on
|
||||
|
|
@ -499,7 +499,9 @@ def _reference_messages(messages: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|||
# mode), and silently skipping the turn would break
|
||||
# user/assistant alternation in the advisory view. Substitute
|
||||
# a placeholder so the reference knows a non-text turn
|
||||
# happened.
|
||||
# happened. Only structured content qualifies — an empty or
|
||||
# whitespace-only STRING turn carries nothing and is dropped
|
||||
# below instead.
|
||||
text = "[user sent non-text content (e.g. an image attachment)]"
|
||||
if not text.strip():
|
||||
# Genuinely empty user turn (content="" / None). It carries
|
||||
|
|
|
|||
|
|
@ -1241,3 +1241,27 @@ def test_reference_guidance_appends_text_part_to_decorated_trailing_user():
|
|||
assert content[0] == marked_part
|
||||
# The guidance rides as a trailing text part outside the cached span.
|
||||
assert content[1] == {"type": "text", "text": "\n\nREFERENCE BLOCK"}
|
||||
|
||||
|
||||
def test_reference_messages_drops_whitespace_only_string_user_turn():
|
||||
"""A whitespace-only STRING user turn is dropped, not placeholdered.
|
||||
|
||||
The non-text placeholder exists for structured content (image-only turns)
|
||||
where a real turn happened that the reference should know about. A bare
|
||||
whitespace string carries nothing — emitting it would 400 strict
|
||||
providers (Kimi/Moonshot 'role user must not be empty'), and
|
||||
placeholdering it would fabricate an attachment that never existed.
|
||||
"""
|
||||
from agent.moa_loop import _reference_messages
|
||||
|
||||
messages = [
|
||||
{"role": "user", "content": " "},
|
||||
{"role": "assistant", "content": "a"},
|
||||
{"role": "user", "content": "real"},
|
||||
]
|
||||
|
||||
view = _reference_messages(messages)
|
||||
|
||||
assert view[0] == {"role": "assistant", "content": "a"}
|
||||
assert view[-1] == {"role": "user", "content": "real"}
|
||||
assert all(str(m["content"]).strip() for m in view)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue